Afficher et animer des images provenant du service web Bing en C++ sur Windows 7

 

Utiliser les services Bing dans une application Win32

L'expert

 
Eric Vernié  
Eric Vernié

 

Dans cet article rédigé par Eric Vernié, nous verrons comment créer une application Win 32 en Visual C++ qui a pour objectif d'afficher et animer des images provenant du web service Bing.

Elements abordés : accélération matérielle (hardware), animation et storyboard, développement asynchrone à l'aide d'agents, Direct2D, DirectWrite, gestionnaire d'animations, librairies d'agent, pattern library, norme ISO ANSI C++0X, agent HTTP, agent BING, agent WIC (Windows Imaging Component), agent Display, agent Store, WWSAPI, WinInet, SOAP, XmlLite.


Introduction :

Dans cet article, nous allons développer une application Windows Win32 en C++, qui utilisera les services de Bing pour rechercher des images et les afficher. L’idée n’est pas des plus originale j’en conviens, mais elle nous permettra de vous montrer la manière de développer une interface réactive, qui ne se bloque pas lorsqu’une demande distante à un service Web est en cours. En un mot comment faire simplement de l’asynchronisme en C++.

Pour notre démonstration, nous aborderons les technologies suivantes :

-        Les API du moteur de recherche de Microsoft Bing, et la manière de solliciter ses services avec les APIWindows Web Services (WWSAPI). Sujet que nous avons traité en détail dans l’article. Consommer les services de Microsoft Translator, et sur lequel nous ne reviendrons pas en détail.

-        WiniNet, pour télécharger les données réelles des images.

-        Les librairies Parallèle et Agent fournies avec Visual Studio 2010, qui nous permettront d’exécuter nos demandes en mode asynchrone.

-        Windows Imaging Component (WIC), Direct2D et DirectWrite pour la création et le rendu des images à l’écran, ainsi que pour la sauvegarde des images sur disques.

-        Windows Animation, pour ajouter un effet d’animation à nos images.

Pré requis

Pour réaliser les exemples de cet article il vous faut :

·        Visual Studio 2010

·        Kit de développement Windows 7

·        Créer une clé Microsoft Bing

Architecture de l’application

L’idée simple de cette application est de requêter les services Bings, pour obtenir des images et les afficher à l’écran. L’enchainement de plusieurs étapes est alors nécessaire.

1)      Récupérer les adresses électroniques (URL) des images.

2)      Télécharger les données brutes des images.

3)      Transformer les données brutes obtenues en bitmap.

4)      Afficher les bitmaps à l’écran.

5)      Stocker les bitmaps.

6)      Animer les bitmaps à l’écran.

7)      Sauvegarder les bitmaps sur disque.

Comme nous pouvons le constater, nous sommes dans un scénario de type pipeline, ou chaque étape doit se faire les unes après les autres. Néanmoins, comme dans notre cahier des charges, nous voulons, une application réactive qui ne se bloque pas, nous ne souhaitons pas attendre qu’une étape soit finie, pour passer à l’étape suivante.

Pour effectuer les différentes étapes en mode asynchrone et pour éviter de bloquer l’interface, un thread sera le producteur, alors qu’un autre thread sera le consommateur. Nous mettrons alors en place le pattern Producteur-Consommateur.

Nous pourrions utiliser les threads et les primitives de synchronisation de base de Windows pour mettre en place ce pattern, mais comme c’est sujet à trop d’erreurs et que nous ne sommes jamais à l’abri d’une perte de montée en charge lors de l’augmentation du nombre de processeurs, il est préférable d’utiliser des librairies qui sont prévues à cet effet. Dans notre exemple, nous utiliserons la librairie Agent Asynchrone, livrée avec Visual Studio 2010, que nous aborderons un peu plus loin.

Pour implémenter ce pipeline, nous allons utiliser la notion d’agent. Chaque agent étant en charge d’une seule tâche.

1.      L’agent Bing est en charge de retrouver les adresses électroniques des images et de les transmettre à l’agent suivant dans le pipeline.

2.      L’agent HTTP reçoit une adresse électronique d’une image et commence à télécharger les données brutes. Comme le téléchargement des images peut prendre du temps (assujettie à la latence d’internet), dès qu’il en télécharge une, il passe la main, à l’agent suivant dans le pipeline, avant de télécharger l’image suivante. Une fois que toutes les images sont téléchargées, il en informe l’agent suivant.

3.      L’agent WIC reçoit les données brutes d’une image sous forme d’un tableau d'octets. A partir de ce tableau d’octets il en crée un bitmap Direct2D puis l’envoie à l’agent suivant. Une fois tous les bitmaps créés, il en informe l’agent suivant.

4.      L’agent Display reçoit un bitmap Direct2D, et l’affiche à l’écran, puis le passe à l’agent suivant. Une fois tous les bitmaps affichés, il en informe l’agent suivant.

5.      L’agent Store a simplement pour but de sauvegarder les bitmaps Direct2D dans un container, et d’envoyer ce container au dernier agent du pipeline.

6.      La dernière étape de notre pipeline, n’est pas à proprement parler un agent, mais il en utilise les mécanismes. Il reçoit le container de bitmaps Direct2D, et en informe la procédure de la fenêtre Windows, pour qu’elle puisse mettre en place le mécanisme d’animation des images.

Architecture grossière de l’application

Architecture grossière de l'application

 

Décomposition du code source

La solution livrée avec cet article se décompose de la manière suivante :

Un répertoire Application :

Contient le fichier AfficherImageBing.cpp, qui inclut le point d’entrée (_tWinMain) de l’application, ainsi que la boucle des messages Windows.

Un répertoire IHM

Qui contient :
La classe FenetrePrincipale. Classe qui a juste pour rôle d’instancier une fenêtre enfant. Nous pourrions tout à fait nous en passer dans notre exemple, mais elle pourra servir à d’autres fins plus tard.

La classe FenetreEnfant. Classe qui va instancier tous les autres modules de l’application. Tous les traitements se font à partir de cette fenêtre. Saisie des mots clés de la recherche, recherche, traitement, stockage et animation des images.

La classe DPEDirectD2D1helper. Classe qui contient tout le code d’accès à Direct2D et à DirectWrite

La classe DPEAnimationHelper. Classe qui contient le code d’accès aux animations Windows

** **

Un répertoire Helper

Qui contient la classe DPEXmlLiteHelper, qui permet de lire la configuration de l’application à partir d’un fichier XML.

Un répertoire ProxyWeb

Qui contient le fichier ProxyClientBing.c, qui contient le code comme son nom l’indique du proxy du service Web Bing.

Un répertoire Agent.

Contenant tous nous agents.
Avant de poursuivre il est important de faire un point sur ce qu’est et à quoi sert un agent.

Définition d’un Agent

En programmation parallèle, l’un des problèmes récurrent et non déterministe qui survient, est le partage d’états entre différentes threads. Ce partage d’états s’il n’est pas bien maitrisé, engendre systématiquement des problèmes de concurrences d’accès. C’est-à-dire qu’une variable, peu à tout moment être dans un état incohérent, et donc générée soit des résultats erronés soit dans le pire des cas, une instabilité de l’application.

Développer avec les agents, va permettre d’outre passer ce problème. En effet, une donnée ou un état seront isolés d’un agent par rapport à l’autre par l’intermédiaire d’un message qui sera véhiculé entre chaque agent.
Un agent envoi (send) le message contenant les informations, l’agent suivant reçois (receive) le message le traite, et envoi un autre message de même nature à l’agent suivant dans le pipeline et ainsi de suite. Comme illustré sur la figure suivante.

Définition d'un agent

 

La méthode receive bloque le thread entrant, tant que le message n’est pas reçu. Il n’y a donc pas besoin de mettre en place des mécanismes complexe de synchronisation type Event par exemple, pour que le message soit transmis correctement d’agent en agent. On peut alors se focaliser sur le fonctionnel de l’agent, plutôt que sur de la plomberie multithreads toujours sujette à erreur.

Les agents permettent de développer des applications parallèles dites à gros grain, et sont basés sur des templates. Pour implémenter un agent il faut procéder comme suit :

1)      Il faut inclure l’entête agent.h et utiliser l’espace de nom Concurrency
#include <agents.h>
using namespace ::Concurrency;

2)      Dériver sa classe de la classe agent
class MonAgent : public agent

3)      Ensuite dans le constructeur, il faut définir un point d’entrée pour envoyer le message avec la méthode send() et un point d’entrée pour recevoir le message avec la méthode receive().
explicit MonAgent(ITarget<RawData>& messageEnvoye,ISource<RawData>& messageRecu);

4)      Puis il faut implémenter la méthode run(). Méthode qui sera appelée directement par la plate-forme agent lorsque l’agent démarrera
void MonAgent::run()
{
    do
    {
          //Le thread bloque ici en attente d'un message
       _data=receive(_messageRecu)
          //Télécharger les données
           TraitementDesDonnees(_data) ;
          //Envoi du message à l’agent suivant dans le pipeline
          send(_messageEnvoye,_data);
       
     }
      while(_data.Sentinel == FALSE)
done();
}

Dans notre exemple, l’agent ne sait pas combien de messages il va recevoir. Il entre donc dans une boucle, qui s’arrêtera lorsqu’une variable (ex Sentinel) est à TRUE, indiquant que l’agent précédant dans le pipeline a envoyé toutes les données et a fini son travail.
Une fois que toutes les données ont été reçues, l’agent met fin à son travail à l’aide de la méthode done().
On notera ici l’implémentation simple et rapide du pattern Producteur/Consommateur.

5)      Enfin pour démarrer l’agent et attendre qu’il ait fini
unbounded_buffer <RawData> msgTargetResponse;
unbounded_buffer <RawData> msgDowloadBitmap;
MonAgent monagent(msgDowloadBitmap, msgTargetResponse)
monagent.start ();
agent::wait (&monagent);

unbounded_buffer<> est une classe qui représente une structure de message asynchrone capable de stocker un nombre illimité de messages. Cette classe conserve les messages dans une file d’attente en mode FIFO, garantissant que celui qui le recevra, les recevra tous et dans le bonne ordre.

Vous noterez également dans notre exemple, que la classe unbounded_buffer prend comme paramètre le type RawData. C’est une structure que nous avons définie, et qui nous permettra de transmettre un état d’agent en agent. Chaque agent remplissant un champ de la structure qui lui est assigné.
struct RawData
{    
HRESULT hr;  //Contient un numéro de l'erreur sous forme de HRESULT
BYTE* BufferVignette; //Pointeur sur le tableau de bytes des vignettes
BYTE* BufferImageComplete;       //Pointeur sur le tableau de bytes de l'image complète
struct ImageResult *ImageBingResult; //Pointeur sur le résultat de la recherche
ID2D1Bitmap *D2D1Thumbnail;      //Pointeur Direct2D sur une vignette
ID2D1Bitmap *D2D1ImageComplete;  //Pointeur Direct2D sur l'image complète
WCHAR Agent[25];    //Contient une chaine de caractère du nom de l'agent
WCHAR Message[1024];//Message de debug
D2D1_RECT_F CurRect;//Contient la position de la vignette à l'écran
BOOL Sentinel;//Variable qui détermine si toutes les images ont été transmises
BOOL EncoursArret;//Est-ce que l'arrêt est demandé
BOOL Sauvegarder; //Est-ce que la sauvegarde du bitmap sous forme de fichier a été fait
}user_rawdata;

Le champ ImageBingResult, sera rempli par l’agent Bing et utilisé par l’agent HTTP.
Les champs BufferVignette et BufferImageComplete, seront remplis par l’agent HTTP et utilisés par l’agent WIC
Les champs D2D1Thumnail et D2D1ImageComplete, seront remplis par l’agent WIC et utilisés par l’agent Display, et ainsi de suite.

Voici en gros les étapes pour construire et démarrer un agent, et tous nos agents dans la suite de cet article reprendront cette structure.

Pour de plus amples informations sur la librairie agent https://msdn.microsoft.com/fr-fr/library/dd492627.aspx

Maintenant que nous connaissons la structure des agents, commençons donc par notre 1er Agent.

L’agent Microsoft BING

L’agent Bing a pour rôle de retrouver les adresses électroniques des images et les fournir à l’agent suivant dans le pipeline. Avant d’aller plus loin, faisons un rappel sur les API de Bing.
Le moteur de recherche sur internet Microsoft Bing , vous permet de faire de la recherche sur le Web, les Images, la vidéo, les news j’en passe et des meilleurs. Comme illustré sur la figure suivante :

L'agent Microsoft BING

 

Avec les API Bing Version 2, vous pourrez ajouter ces fonctionnalités de recherches à votre application.

Plusieurs protocoles sont disponibles pour accéder au service, JSON, XML et SOAP, c’est ce dernier que nous utiliserons.

Pour utiliser le protocole SOAP, dans notre application, il nous faut obtenir la structure du service en lui-même. Cette structure contient la définition même du service, les noms et signature de méthodes, les types de retour, etc.

La définition du service est définie dans un fichier au format WSDL (Web Service Description Language) et obtenue à l’adresse http://api.bing.net/search.wsdl?AppID=APPID

Le paramètre APPID est un global unique identifier que vous devez créer pour votre application à l’adresse suivante : https://www.bing.com/developers/createapp.aspx pour de plus amples détails sur la manière d’obtenir cet APPID voir l’article Consommer les services Microsoft Translator via les nouvelles API WWSAPI de Windows 7 dans une application Win32

Pour manipuler les services Microsoft Bing, il faut dans un premier temps définir la source de la recherche, à l’aide de la structure SearchRequest.

Cette structure devra être définie entre autre avec :

·        Le numéro d’identification de l’application, l’APPID

·        Le texte de la requête

·        Le ou les types de sources que l’on souhaite obtenir. Différents SourceType sont possibles, tels que, la vidéo, les images, le web, les news, la publicité, la traduction, et autres que je vous laisse découvrir à cette adresse https://msdn.microsoft.com/en-us/library/dd251056.aspx

·        Et d’autres options, telle que Adult qui définit la restriction de la recherche à du contenu visible ou non.

Une fois la requête exécutée, la réponse est lisible dans la structure SearchResponse.
Cette structure possède comme membre d’autres structures :

·        Qui contiendront les données en fonction du ou des SourceType choisies. Nous y retrouverons les structures WebResponse, ImageResponse, VideoResponse,TranslationResponse, etc.
Chaque structure ayant ses propres membres qui ne sont pas toujours les mêmes en fonction du SourceType.

·        Une structure de type Error, qui contiendra les éventuelles erreurs survenues lors de l’appel.

Pour télécharger le SDK de Bing https://www.bing.com/developers/appids.aspx

Création du proxy Bing

La création du proxy Bing ce fait en deux étapes :

Tout d’abord, nous utilisons l’utilitaire svcutil.exe ; disponible avec le SDK de Windows 7 ; en lui passant l’adresse de la description du Service Microsoft Bing

svcutil /t:metadatahttp://api.bing.net/search.wsdl?AppID=\<VOTREAPPID>

L‘outil crée en local le fichier de description du service Microsoft Bing

schemas.microsoft.com.LiveSearch.2008.03.Search.wsdl

Ce fichier au format XML, contient toute la description du service, comme illustré sur la figure suivante.

Extrait du fichier WSDL

Création du Proxy Bing

 

La seconde étape consiste à créer à partir du fichier WSDL le code source, ainsi que les fichiers d’entêtes du proxy client que nous utiliserons dans la section suivante. Pour cela on utilise l’outil WSUTIL de la manière suivante :

wsutil.exe *.wsdl

 

wsutil, est en charge de transformer la description au format wsdl en un format natif langage C. Les deux fichiers suivants sont alors créés.

schemas.microsoft.com.LiveSearch.2008.03.Search.wsdl.h

schemas.microsoft.com.LiveSearch.2008.03.Search.wsdl.c

Remarque :

Vous trouverez les outils svcutil et wsutil dans le répertoire C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin.

Le fichier .H contient toutes les définitions des méthodes, structures et énumération que nous utiliserons.

Par exemple, les structures SearchRequest et SearchResponse sont définies ainsi :

typedef struct SearchRequest

{

*    WCHAR* Version;*

*    WCHAR* Market;*

*    WCHAR* UILanguage;*

*    WCHAR* Query;*

*    WCHAR* AppId;*

*    enum AdultOption Adult;*

*    double Latitude;*

*    double Longitude;*

*    doubleRadius;*

*    unsigned int OptionsCount;*

*    __field_ecount_opt(OptionsCount)enum SearchOption* Options;*

*    unsigned int SourcesCount;*

*    __field_ecount(SourcesCount)enum SourceType* Sources;*

*    struct WebRequest* Web;*

*    struct ImageRequest* Image;*

*    struct PhonebookRequest* Phonebook;*

*    struct VideoRequest* Video;*

*    struct NewsRequest* News;*

*    struct MobileWebRequest* MobileWeb;*

*    struct TranslationRequest* Translation;*

} SearchRequest;

typedef struct SearchResponse

{

*    WCHAR* Version;*

*    struct Query* Query;*

*    struct SpellResponse* Spell;*

*    struct WebResponse* Web;*

*    struct ImageResponse* Image;*

*    struct RelatedSearchResponse* RelatedSearch;*

*    struct PhonebookResponse* Phonebook;*

*    struct VideoResponse* Video;*

*    struct InstantAnswerResponse* InstantAnswer;*

*    struct NewsResponse* News;*

*    struct MobileWebResponse* MobileWeb;*

*    struct TranslationResponse* Translation;*

*    unsigned int ErrorsCount;*

*    __field_ecount_opt(ErrorsCount)struct Error* Errors;*

} SearchResponse;

Le fichier .C contient l’implémentation des méthodes, telle que la méthode Search.

HRESULT WINAPI LiveSearchPortBinding_Search(

*    __in WS_SERVICE_PROXY* _serviceProxy,*

*    __in SearchRequest* parameters0,*

*    __out SearchResponse** parameters,*

*    __in WS_HEAP* _heap,*

*    __in_ecount_opt(_callPropertyCount) const WS_CALL_PROPERTY* _callProperties,*

*    __in const ULONG _callPropertyCount,*

*    __in_opt const WS_ASYNC_CONTEXT* _asyncContext,*

*    __in_opt WS_ERROR* _error)*

{

*    void* _argList[2];*

*    _argList[0] = &parameters0;*

*    _argList[1] = &parameters;*

*    return WsCall(_serviceProxy,*

*        (WS_OPERATION_DESCRIPTION*)&schemas_microsoft_com_LiveSearch_2008_03_Search_wsdlLocalDefinitions.contracts.LiveSearchPortBinding.LiveSearchPortBinding_Search.LiveSearchPortBinding_Search,*

*        (const void **)&_argList,*

*        _heap,*

*        _callProperties,*

*        _callPropertyCount,*

*        _asyncContext,*

*        _error);*

}

Utilisation de WWSAPI

Je ne vais pas rentrer une nouvelle fois dans les détails des API WWSAPI, vous les retrouverez dans l’article Consommer les services Microsoft Translator via les nouvelles API WWSAPI de Windows 7 dans une application Win32. Néanmoins, voici les étapes à suivre.

1.      Il faut inclure l’entête de WWSAPI
#include <WebServices.h>

2.      Se lier à la librairie WebServices.lib

3.      Ensuite dans le code, il faut initialiser et ouvrir un proxy, à l’aide des méthodes LiveSearchPortBinding_CreateServiceProxy et WsOpenServiceProxy

HRESULT DPEBingAgent::InitialiserProxyWeb ()
{
      HRESULT hr;        
      hr=WsCreateError (NULL,0,&_wsError);
      if (FAILED(hr)) return hr;      
      //SIZE_T_MAX évite le message d'erreur dépassement du quota
      hr=WsCreateHeap (SIZE_T_MAX,0,NULL,0,&_wsHeap,_wsError);
             if (FAILED(hr))
             {
                    LibererRessourcesServiceWeb();
                    return hr;
             }                  
      WS_HTTP_BINDING_TEMPLATE templateValue={};
**      hr=LiveSearchPortBinding_CreateServiceProxy (  
    &templateValue,
    NULL,0,
    &_wsServiceProxy,
    _wsError);
*

      if (FAILED(hr))
      {
             LibererRessourcesServiceWeb();
             return hr;
      }

      WS_ENDPOINT_ADDRESS address={}; 
      WS_STRING Url=WS_STRING_VALUE(L"http://api.search.live.net:80/soap.asmx");
      address.url=Url;
**      hr=WsOpenServiceProxy (_wsServiceProxy ,&address,NULL,_wsError);**
      if (FAILED(hr))
      {
             LibererRessourcesServiceWeb();  
             return hr;
      }
      //Initialise la clé pour le Service Microsoft Bing
      wcscpy_s (_APPID,_countof(_APPID),L**"[Votre APPID]"**);
      return hr;
}*

4.      Initialiser la structure SearchRequest

SearchRequest *_request;
SourceType _type[1]
void DPEBingAgent::InitialiserServiceWeb()
{
_request=new SearchRequest();
_request->AppId =_APPID; 
_request->Image =new ImageRequest ();
_request->Adult =AdultOptionStrict;          
_type[0]=SourceTypeImage;
_request->Sources =_type;
_request->SourcesCount=1;
}

Ici nous initialisons un SourceType de type Image.

5.      S’il y a initialisation de ressources, il faut également libérer les ressources, avec les API WWSAPI associées
void DPEBingAgent::LibererRessourcesServiceWeb ()
{

if (_wsError!=nullptr)
{           
      WsFreeError (_wsError);
}
if (_wsHeap!=nullptr)
      WsFreeHeap (_wsHeap);    
if (_wsServiceProxy !=nullptr)
{           
      WsCloseServiceProxy (_wsServiceProxy,nullptr,_wsError);
      WsFreeServiceProxy (_wsServiceProxy);
}
if (_request!=nullptr)
      delete _request;

}

6.      Ensuite, il faut effectuer la recherche en utilisant la méthode LiveSearchPortBinding_Search
 
HRESULT DPEBingAgent::RechercherImage(WCHAR *requete,
                                         int nombreVignettes,
                                         int offset)
{
HRESULT hr;

*//Ici on complète la requête avec le texte à rechercher
//et le nombre de vignettes à retrouver
_request->Query =requete;
_request->Image->Count =nombreVignettes;     
//l'offset correspond au décalage du nombre de vignettes déjà retrouvées.
// Ex. 1er demande de 15 Vignettes=1er Page => offset=0
//       2ieme demande de 15 Vignettes=2de Page => offset=15
//       3ieme demande de 15 Vignettes=3ieme Page => offset=30
//       etc..
_request->Image->Offset=offset;
//Initialisation de la classe SearchRespons
_response=new SearchResponse();              *

//Exécute en mode synchrone la recherche
//ici ce n'est pas la peine d'utiliser le mode asynchrone
//du service Web défini par WS_ASYNC_CONTEXT et WS_ASYNC_CALLBACK
//car c'est la structure de type agent qui nous la fournit de base.
hr=LiveSearchPortBinding_Search (_wsServiceProxy,
                                 _request,
                                 &_response ,
                                 _wsHeap,
                                 NULL,
                                 0,
                                 NULL,
                                 _wsError);  
return hr; }

Implémentation de l’agent Bing

1.      L’agent bing est le premier du pipeline, il prendra comme paramètre dans son constructeur :
Un point d’entrée pour envoyer un message :
ITarget<RawData>& messageEnvoye
Un point d’entrée pour recevoir un éventuel message d’arrêt :
ISource<BOOL>& stop
Une structure : WebServiceConfig,
struct WebServiceConfiguration
{
      WCHAR Requete[1024];
      int NombreVignettes;
      int NumeroPage;
      int TotalPages;
      int TotalVignettes;
      BOOL NouvelleRequete;
      BOOL Sauvegarder;

};
qui contiendra, entre autre, le texte de la requête, le nombre de vignettes à télécharger et le numéro de page à utiliser.
class DPEBingAgent_API DPEBingAgent : public DPEBaseAgent
{
public :
explicit DPEBingAgent(ITarget<RawData>& messageEnvoye,
                          ISource<BOOL>& stop,
                          WebServiceConfiguration config);
protected:  
      void run();

//Code omis pour plus de clarté
}
Le constructeur s’implémente de la manière suivante :
DPEBingAgent::DPEBingAgent(ITarget<RawData> &messageEnvoye,
                                 ISource<BOOL>& stop,
                                 WebServiceConfiguration config)
      :_messageEnvoye(messageEnvoye),
      _stop(stop),
      _config(config),
      _wsError(nullptr),
      _wsHeap(nullptr),
      _wsServiceProxy(nullptr)
{
            
      InitialiserProxyWeb();
      InitialiserServiceWeb();
}*

Vous noterez que notre agent Bing dérive de DPEBaseAgent qui est en fait une classe qui dérive elle-même de la classe agent, et qui propose des services supplémentaires que nous réutiliserons dans tous nos agents. Je ne rentrerai pas dans le détail de ses services, mais vous trouverez avec cet article, le code complet de l’application.

2.      Enfin on implémente la méthode run() de la classe agent
void DPEBingAgent::run()
{

HRESULT hr;
//Au démarrage de l'agent
//Initialise la structure RawData (_data)    
_data.Sentinel=FALSE;//Permet de définir s’il y a encore des données à télécharger                           *
//Code omis pour plus de clarté

//Télécharge une page de NombreVignettes
//en n'oubliant pas de passer le bon offset si l'utilisateur
//veut une autre page offset=numePage-1*NombreVignettes
hr=this->RechercherImage (_config.Requete,
                          _config.NombreVignettes,(
                          _config.NumeroPage-1) *_config.NombreVignettes);
            
//Code omis pour plus de clarté
int count=_request->Image->Count;
//Envoi vignette par vignette à l'agent suivant dans le pipeline
for(int i=0; i<count ;i++)
{
      //Code omis pour plus de clarté

*      //Si on arrive à la fin
      if ((i+1)==count)
      {
             _data.Sentinel =TRUE;
      }
      //Copier la réponse dans la structure RawData _data
      CopierReponse(i);
      //Envoyer le message à l'agent suivant dans le pipeline
      BOOL bresult=asend(_messageEnvoye,_data);
}           
done();            
}*

La méthode run(), appel la méthode RechercherImages(), qui retourne si tout va bien le nombre d’images dans une structure de type SearchResponse
Le nombre de réponses, nous servira comme nombre d’itérations de notre boucle. A chaque itération, nous copions (CopierReponse()) une réponse dans la structure RawData, que nous envoyons asend() au prochain agent dans le pipeline.

Note : La méthode asend() diffère de la méthode send(), dans le sens où elle envoie le message en mode asynchrone, c’est-à-dire qu’elle n’attend pas l’éventuelle réponse de l’agent suivant dans le pipeline.

La méthode CopierReponse(), récupère les adresses électroniques des images à partir de la structure SearchResponse, et les copies dans les structures ImageResult, et Thumbnail
*typedef struct ImageResult
{
    WCHAR* Title;
    WCHAR* MediaUrl;
    WCHAR* Url;
    WCHAR* DisplayUrl;
    unsigned int Width;
    unsigned int Height;
    unsigned int FileSize;
    WCHAR* ContentType;
    struct Thumbnail* Thumbnail;
} ImageResult;

typedef struct Thumbnail
{
    WCHAR* Url;
    WCHAR* ContentType;
    unsigned int Width;
    unsigned int Height;
    unsigned int FileSize;
    unsigned int RunTime;
} Thumbnail;*
Comme vous pouvez le constater, ces deux structures contiendront les informations nécessaires, Url, Taille, largeur et hauteur des images etc., qui seront utilisées par les agents suivants dans le pipeline.

A la dernière itération, nous informons (_data.Sentinel=TRUE) l’agent suivant, que le nombre de réponses total a été atteint. Enfin on signale que l’agent a terminé son travail avec la méthode done().

Note : Le commentaire //Code omis pour plus de clarté, correspond au code de gestion d’erreur, qui permet si une erreur survient, de diffuser l’erreur d’agent en agent, et surtout d’arrêter l’exécution du pipeline.

L’agent HTTP

L’agent HTTP, a pour rôle, de télécharger en fonction d’une adresse électronique fournie par l’agent BING, les données brutes d’une image, et les envoyer à l’agent suivant dans le pipeline.

Pour pouvoir télécharger des images provenant du résultat du service Bing, nous avons à notre disposition dans Windows 7, deux APIS, l’API WinINet http ://msdn.microsoft.com/en-us/library/aa385483(v=VS.85).aspx et WinHTTP http ://msdn.microsoft.com/en-us/library/aa384273(v=VS.85).aspx

Dans notre exemple, nous utiliserons l’API WinINET, car notre application n’est pas destinée à tourner sur un serveur. Par contre, il sera préférable d’utiliser l’API WinHTTP, si vous souhaitez développer un service, ou faire tourner du code sur un serveur.

Utilisation de WinINet

1.      Tout d’abord il faut inclure l’entête WinInet.h
#include <WinInet.h>

2.      Se lier à la librairie Wininet.lib

3.      Ensuite il faut ouvrir une session internet avec l’API InternetOpen()
HINTERNET _hSession
_hSession=InternetOpen(L"WinInet agent V 0.5 (EV)",       INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0);

4.      Avec le handle _hSession obtenue, on ouvre une adresse électronique à l’aide de la méthode InternetOpenUrl().
HINTERNET hOpenUrl=0;
hOpenUrl=InternetOpenUrl (_hSession,
                           Url.c_str (),
                           NULL,
                           0,
                           INTERNET_FLAG_EXISTING_CONNECT,
                           NULL);

Ou le paramètre Url.c_str() contient l’adresse électronique de l’image à télécharger. Souvenez-vous cette adresse est fourni par l’agent Bing.

5.      Ensuite, il faut lire les données brutes à l’aide de la méthode InternetReadFile()
BOOL bResult=FALSE;
DWORD lpdwNumberOfBytesRead=0
bResult=InternetReadFile(hOpenUrl, pBuffer, dwNumberOfByteToRead, &lpdwNumberOfBytesRead);

hOpenUrl, est un handle qui provient de l’ouverture de l’Url à l’étape 3
pBuffer, est un tableau d’octets qui a été initialisé à la taille de l’image Bing, et que nous sauvegarderons dans notre structure RawData et que nous fournirons à l’agent suivant dans le pipeline
dwNumberOfByteToRead, correspond au nombre d’octets à lire.
lpdwNumberOfBytesRead, correspond au nombre d’octets effectivement lues

6.      Enfin il faut libérer les deux handles _hSession et hOpenUrl que nous avons ouvert.
InternetCloseHandle(hOpenUrl);
InternetCloseHandle(_hSession);

*Listing complet de la méthode de téléchargement des données brutes.
HRESULT DPEHttpAgent::TelechargerDonneesBrutes(wstring Url,
                                               BYTE* pBuffer,
                                               DWORD dwNumberOfByteToRead)
{    *

HRESULT hr=S_OK;
BOOL bResult=FALSE;
DWORD lpdwNumberOfBytesRead=0;
HINTERNET hOpenUrl=0;
if (_hSession)
{
      hOpenUrl=InternetOpenUrl (_hSession,
                           Url.c_str (),
                           NULL,
                           0,
                           INTERNET_FLAG_EXISTING_CONNECT,
                           NULL);
      if (hOpenUrl)
      {
             ZeroMemory (pBuffer,dwNumberOfByteToRead);
             bResult=InternetReadFile(hOpenUrl,
                                      pBuffer,
                                     dwNumberOfByteToRead,
                                      &lpdwNumberOfBytesRead);
                   
                    if (bResult==FALSE)
                    {
                          _data.hr=E_FAIL;
                          wcscpy(_data.Message,L"Impossible de lire les données du fichier");
                    }
             }
             else
             {
                    _data.hr=E_FAIL;
                    wcscpy(_data.Message,L"Impossible d'accéder à l'URL");
             }
            
      }
      else
      {
             _data.hr=E_FAIL;
             wcscpy(_data.Message,L"Impossible d'ouvrir l'agent WinInet agent V 0.5 (EV)");
      }
      if (hOpenUrl)
             InternetCloseHandle(hOpenUrl);
      return hr;
}

Implémentation de l’Agent HTTP

1.      L’agent HTTP est le second agent du pipeline, il recevra un message de l’agent Bing contenant les adresses électroniques à télécharger et enverra un message à l’agent suivant dans le pipeline contenant les données brutes de l’image. Son constructeur prendra alors comme paramètres :
Un point d’entrée pour envoyer un message :
ITarget<RawData>& messageEnvoye
Un point d’entrée pour recevoir le message de l’agent Bing :
ISource<RawData>& messageRecu ; Un point d’entrée pour recevoir un éventuel message d’arrêt :
ISource<BOOL>& stop
Une variable booléen telechargerImageComplete indiquant si il faut télécharger la vignette ou l’image complète :

classDPEHttpAgent:public DPEBaseAgent
{
public:
      explicit DPEHttpAgent(ITarget<RawData>& messageEnvoye,
                           ISource<RawData>&messageRecu,
                           ISource<BOOL>& stop,
                           BOOL telechargerImageComplete);
protected:  
      void run();

//Code omis pour plus de clarté
}
Le constructeur s’implémente de la manière suivante :
DPEHttpAgent::DPEHttpAgent(ITarget<RawData>& messageEnvoye,
                          ISource<RawData>& messageRecu,
                          ISource<BOOL>& stop,
                           BOOL telechargerImageComplete)
      :_messageEnvoye(messageEnvoye),
      _messageRecu(messageRecu),
      _stop(stop),
      _telechargerImageComplete(telechargerImageComplete),
      _hSession(0)
     
{
     
      _hSession=InternetOpen(L"WinInet agent V 0.5 (EV)",      INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0);
}

2.      Enfin on implémente la méthode run()
voidDPEHttpAgent::run()
{
      //Code omis pour plus de clarté
      do
      {
             //L’agent bloque ici en attente d'un message
             _data=receive(_messageRecu);          
**             //Code omis pour plus de clarté
*
             UINT fileSize;                        
             wstring url;       
                       //Est-ce que je dois télécharger l'image complète ?                      if (_telechargerImageComplete)
             {
                    url.append (_data.ImageBingResult->MediaUrl);
                    fileSize=_data.ImageBingResult->FileSize;
                    _data.BufferImageComplete=(BYTE*)malloc(fileSize);
                   _data.hr=TelechargerDonneesBrutes(url,
                                              _data.BufferImageComplete,
                                              fileSize);
             }
             else
             {
                    url.append (_data.ImageBingResult->Thumbnail->Url);
                    fileSize=_data.ImageBingResult->Thumbnail->FileSize ;                    //Télécharger la vignette
                    _data.BufferVignette=(BYTE*)malloc(fileSize);
                    _data.hr =TelechargerDonneesBrutes (url,
                    _data.BufferVignette, fileSize);   
             }
             //Code omis pour plus de clarté
             //Envoyer le message à l'agent suivant dans le pipeline
             asend(_messageEnvoye,_data);     }
      while(_data.Sentinel ==FALSE);
      done();     
}*
Vous noterez ici, que la méthode run() rentre dans une boucle qui se finira lorsque la variable Sentinel de notre message sera à TRUE. C’est l’agent précédant dans le pipeline en l’occurrence l’agent BING, qui en avertira notre agent HTTP.
A l’initialisation de la boucle, l’agent HTTP, attend de recevoir (_data=receive()) un message. Tant que le message n’est pas envoyé par l’agent BING, la méthode receive() bloque l’appel. Dès qu’un message est reçu, l’agent HTTP, va télécharger en fonction d’une adresse électronique fourni par l’agent BING, les données brutes de l’image. Données brutes qu’il sauvegarde dans la structure RawData et l’envoi immédiatement à l’agent suivant dans le pipeline. Enfin l’agent signale qu’il a fini son travail, à l’aide de la méthode done().

L’agent WIC

L’agent WIC, a pour rôle de transformer les données brutes fournies par l’agent HTTP, en bitmap Direct2D. Il utilise la technologie Windows Imaging Component (WIC) pour le faire.
WIC étant une plateforme extensible (permettant de développer son propre codec), qui offre un jeu d’interfaces standard permettant de décoder ou d’encoder des images et ceci quelques soit le format (TIFF, JPEG, PNG, GIF, BMP etc.).

Pour de plus amples informations sur Windows Imaging Component, reportez-vous à la documentation MSDN : https://msdn.microsoft.com/fr-fr/library/ee719902(v=VS.85).aspx

Utilisation de WIC pour décoder des données brutes et en créer un bitmap Direct2D

1.      Tout d’abord il faut inclure l’entête WIC
#include <wincodec.h>

2.      Se lier à la librairie windowscodecs.lib

3.      WIC est un composant COM, il est donc important d’initialiser COM avant de faire appel à ses API
_data.hr=CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
Même si vous avez déjà initialisé COM dans l’application, n’oubliez pas ici que nous utiliserons WIC dans un agent qui s’exécute dans un autre thread. Il donc important d’initialiser COM pour le thread courant.

4.      Pour manipuler WIC, il faut commencer par créer une fabrique symbolisée par l’interface IWICImagingFactory
IWICImagingFactory *_pWicFactory;
_data.hr = CoCreateInstance(
        CLSID_WICImagingFactory,
        NULL,
        CLSCTX_INPROC_SERVER,
             IID_PPV_ARGS(&_pWicFactory)
        );

5.      Comme nous voulons créer un bitmap à partir d’un tableau d'octets, nous allons passer par un flux de données symbolisé par l’interface IWICStream.
IWICStream *_pStream;
hr=_pWicFactory->CreateStream(&_pStream);

Ensuite nous initialisons le flux avec les données brutes de l’image
hr = _pStream->InitializeFromMemory(      reinterpret_cast<BYTE*>(_data.BufferVignette ),
                          _data.ImageBingResult->Thumbnail->FileSize);

6.      Ensuite nous créons un décodeur, à partir du flux de données IWICStream que nous venons d’initialiser.
IWICBitmapDecoder *_pDecoder ;
hr = _pWicFactory->CreateDecoderFromStream(
            _pStream,
            NULL,
            WICDecodeMetadataCacheOnDemand ,
            &_pDecoder
            );

7.    A partir de ce décodeur, nous obtenons la 1ere frame de l’image qui nous servira pour la conversion de l’image.
IWICBitmapFrameDecode *_pFrameSource ;
hr = _pDecoder->GetFrame(0, &_pFrameSource);

La méthode GetFrame() retrouve la première frame de l’image, et en créée une source de type IWICBitmapFrameDecode qui dérive de IWICBitmapSource.

8.      Pour convertir l’image il nous faut créer un convertisseur symbolisé par l’interface IWICFormatConverter, puis ensuite initialiser le convertisseur avec le bitmap source. Ce convertisseur nous permet ici de convertir l’image IWICBitmapFrameDecode en une image au format 32 bits. Pour de plus amples informations sur les formats gérés par WIC https://msdn.microsoft.com/fr-fr/library/ee719797(v=VS.85).aspx
.
IWICFormatConverter *_pConverter;
hr = _pWicFactory->CreateFormatConverter(&_pConverter);

hr = _pConverter->Initialize(
                _pFrameSource,
                GUID_WICPixelFormat32bppPBGRA,
                WICBitmapDitherTypeSolid,
                NULL,
                0.f,
                WICBitmapPaletteTypeMedianCut
                );

9.      Une fois le convertisseur initialisé, on le passe à la méthode Direct2D CreateBitmapFromWicBitmap qui crée le bitmap et le sauvegarde dans le champ D2D1Thumbnail de notre structure RawData qui sera transmis à l’agent suivant dans le pipeline.
ID2D1RenderTarget *_pRender;
hr = _pRender->CreateBitmapFromWicBitmap (_pConverter,nullptr,
                                          &_data.D2D1Thumbnail);

Nous verrons au chapitre suivant comment créer l’objet Direct2D ID2D1RenderTarget qui nous permettra d’effectuer le rendu.

10.   Enfin il faut libérer les ressources utilisées.
void DPECreateD2D1BitmapAgent::LibererRessources ()
{

*      SafeRelease(&_pStream);         
      SafeRelease(&_pDecoder);
    SafeRelease(&_pFrameSource);
    SafeRelease(&_pConverter);
}*
La méthode SafeRelease étant implémentée de la manière suivante :
template<class Interface>
inline void
SafeRelease(Interface **ppInterfaceToRelease)
{
    if (*ppInterfaceToRelease != NULL)
    {
        (*ppInterfaceToRelease)->Release();
        (*ppInterfaceToRelease) = NULL;
    }
}

Implémentation de l’agent WIC

1.      L’agent WIC reçoit un message de l’agent HTTP contenant les données brutes de l’image, et envoie un message contenant un bitmap Direct2D à l’agent suivant dans le pipeline. Son constructeur prendra alors comme paramètres :
Un point d’entrée pour envoyer un message :
ITarget<RawData>& messageEnvoye
Un point d’entrée pour recevoir le message de l’agent Bing :
ISource<RawData>& messageRecu ; Une variable booléen imageComplete indiquant si l’on doit décoder la vignette ou l’image complète.
Un pointeur sur le contexte de rendu Direct2D
ID2D1RenderTarget *render

classDPECreateD2D1BitmapAgent : public DPEBaseAgent
{
public:
      explicit DPECreateD2D1BitmapAgent(ITarget<RawData>& messageEnvoye,
                                        ISource<RawData>& messageRecu,
                                        BOOL imageComplete,
                                        ID2D1RenderTarget *render);
      ~DPECreateD2D1BitmapAgent(void);
protected :
      void run();
//Code omis pour plus de clarté
}

2.      La méthode run(), s’implémente de la manière suivante :
voidDPECreateD2D1BitmapAgent::run()
{
     
      _data.hr=InitialiserFabriqueImage ();
      //Code omis pour plus de clarté
      do
      {
             //L'agent bloque ici en attente d'un message
             _data=receive(_messageRecu);
             //Code omis pour plus de clarté
             _data.hr=DecoderImage();
             //Code omis pour plus de clarté 
             asend (_messageEnvoye,_data);
      }
      while(_data.Sentinel ==FALSE);
     
      done();
}

La méthode run() rentre dans une boucle qui se finira lorsque la variable Sentinel du message RawData sera à TRUE. Une fois l’image décodée, le message est envoyé immédiatement à l’agent suivant. Enfin l’agent signale qu’il a fini avec la méthode done().

L’agent Display

L’agent Display, a pour simple rôle d’afficher à l’écran les vignettes au format d’un bitmap direct2D. Affichage qui sert uniquement d’indicateur une espèce de sablier indiquant que le téléchargement est en cours. Le réel affichage sous forme de grille de vignettes, se fera toujours à l’aide de Direct2D mais en associations avec les animations Windows comme nous le verrons plus tard.

Direct2D est une API pour la création de graphiques 2D. Basée sur Direct3D 10, elle offre aux développeurs Win32, une API indépendante de la résolution, et qui utilise la puissance des cartes graphiques de nouvelle génération. Vous pouvez, avec Direct2D, améliorer vos applications GDI existantes, sans être obligé de les réécrire. En effet Direct2D a été conçu pour coopérer avec le GDI et d'autres technologies graphiques. Enfin, Direct2D vous permet d'écrire du contenu Direct2D sur une surface GDI.

Pour de plus amples informations sur Direct2D, reportez-vous à la documentation MSDN : https://msdn.microsoft.com/fr-fr/library/dd370990(v=VS.85).aspx

Dans notre exemple, l’agent Display (ainsi que l’agent WIC) ne font qu’utiliser le contexte de rendu Direct2D. Voici comment en créer un.

Création du contexte de rendu Direct2D

Pour créer un contexte de rendu Direct2D il faut :

1.      Inclure le fichier d’entête D2D1.h
#include <D2D1.h>

2.      Se lier à la librairie D2D1.lib

3.      Créer une fabrique qui permettra de créer tous les objets Direct2D.
ID2D1Factory *_pfabriqueD2D1;
D2D1CreateFactory (D2D1_FACTORY_TYPE_MULTI_THREADED,
                    &_pfabriqueD2D1);

Une fois que la fabrique est créée, nous allons nous en servir pour créer notre contexte de rendu.
ID2D1HwndRenderTarget *_pcontexteRenduD2D1;
RECT rc;
GetClientRect(hWnd,&rc);    
D2D1_SIZE_U usize = D2D1::SizeU((rc.right - rc.left),(rc.bottom - rc.top));   
//Création du contexte de rendu
D2D1_RENDER_TARGET_PROPERTIES proprietesRendu=D2D1::RenderTargetProperties();
D2D1_HWND_RENDER_TARGET_PROPERTIES proprietesHwndRendu=D2D1::HwndRenderTargetProperties (hWnd,usize);
hr=_pfabriqueD2D1->CreateHwndRenderTarget (proprietesRendu,
             proprietesHwndRendu,                                  &_pcontexteRenduD2D1);

La méthode CreateHwndRenderTarget, prend comme paramètre une structure D2D1_RENDER_TARGET_PROPERTIES, qui est initialisée avec des valeurs par défaut, à l’aide de la méthode D2D1 ::RenderTargetProperties()
Ces valeurs par défaut, permettent de fonctionner avec la plus part des matériels de rendu et est initialisée de la manière suivante :
proprietesRendu.type =D2D1_RENDER_TARGET_TYPE_DEFAULT;
proprietesRendu.dpiX =0.0f;
proprietesRendu.dpiY =0.0f;
proprietesRendu.pixelFormat.format =DXGI_FORMAT_UNKNOWN;
proprietesRendu.pixelFormat.alphaMode=D2D1_ALPHA_MODE_UNKNOWN;
proprietesRendu.usage=D2D1_RENDER_TARGET_USAGE_NONE;
proprietesRendu.minLevel =D2D1_FEATURE_LEVEL_DEFAULT ;

Le champ type, correspond au type de rendu que vous souhaité, soit matériel, D2D1_RENDER_TARGET_TYPE_HARDWARE, soit logiciel D2D1_RENDER_TARGET_TYPE_SOFTWARE
Dans notre exemple la valeur par défaut, indique que Direct2D utilisera le rendu matériel si la carte graphique le supporte. Vous pouvez forcer la valeur du champ type à l’une ou l’autre des valeurs, et vérifier leurs effets sur l’utilisation de la CPU, comme illustré sur les figures suivantes

Utilisation de l'accélération hardware Utilisation du CPU
Utilisation de l'accélération hardware Utilisation du CPU

 

On voit dans ces deux images que l’utilisation de la CPU est d’environ 5% lors de l’utilisation matériel (sous entendu c’est la carte graphique qui bosse) alors qu’elle est de 79% lors d’un rendu logiciel.

Les champs dpiX et dpiY, sont initialisés à zéro, indiquant que nous utilisons la taille de l’affichage par défaut. (à ne pas confondre avec la résolution de l’écran). Vous devez savoir que Windows 7 introduit la technologie Hight-DPI qui permet d’augmenter la lisibilité de lecture à l’écran. L’affichage d’un écran est de base de 96 DPI, mais avec cette technologie il est possible de monter à 120 ou à 144 DPI, comme illustré sur la figure suivante :
125% correspondant à 120 DPI (96*125/100), 150% correspondant à 144 DPI (96*150/100)

High DPI

Sur notre configuration les champs dpiX et dpiY prendront comme valeur par défaut 120.
Le champ pixelFormat correspondant au format de l’image que nous voulons, la valeur est par défaut, car nous ne connaissons pas à l’avance le format des images qui seront téléchargées.
Le champ usage, permet de définir, si le bitmap sera rendu localement et envoyé au client terminal service (D2D1_RENDER_TARGET_USAGE_FORCE_BITMAP_REMOTING), mais il permet de définir également si le contexte de rendu peut être utilisé avec le GDI (D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE)
Le champ minLevel, permet de déterminer le niveau de Direct3D.
D2D1_FEATURE_LEVEL_9         = D3D10_FEATURE_LEVEL_9_1,D2D1_FEATURE_LEVEL_10        = D3D10_FEATURE_LEVEL_10_0

Remarque : Tout le code le code Direct2D est implémenté dans la classe DPEDirectD2D1Helper

Implémentation de l’agent Display

1.      L’agent Display reçoit un message de l’agent WIC contenant un bitmap Direct2D à afficher et envoie un message contenant un bitmap Direct2D à l’agent suivant dans le pipeline.
Son constructeur prendra alors comme paramètres :
Un point d’entrée pour envoyer un message :
ITarget<RawData>& messageEnvoye
Un point d’entrée pour recevoir le message de l’agent Bing :
ISource<RawData>& messageRecu ; Un pointeur sur le contexte de rendu Direct2D**
ID2D1RenderTarget *render**

*classDPEDisplayAgent : public DPEBaseAgent
{
public:
      explicit DPEDisplayAgent(ITarget<RawData>& messageEnvoye,
                                               ISource<RawData>& messageRecu,
                                               ID2D1RenderTarget *pRender);

*      ~DPEDisplayAgent(void);
     
protected :
      void run();
//Code omis pour plus de clarté
}*

2.      La méthode run() s’implémente alors de la manière suivante :
voidDPEDisplayAgent::run()
{
      //Récupère la taille de la zone cliente sur laquelle
      //sera rendu les images
      D2D1_SIZE_F zoneClientSize= _pRender->GetSize ();   do
      {
             //L'agent bloque ici en attente d'un message
             _data=receive(_messageRecu);
             //Code omis pour plus de clarté 
             if (SUCCEEDED(_data.hr))
             {
                    //récupère la taille de l'image
                    D2D1_SIZE_F size =_data.D2D1Thumbnail->GetSize ();
                    //Affiche les bitmaps en tas
                    D2D1_POINT_2F upperLeftCorner =
                                 D2D1::Point2F(zoneClientSize.width/2,
                                               zoneClientSize.height/4);                                               _pRender->BeginDraw ();
                                 _pRender->DrawBitmap (_data.D2D1Thumbnail,
                                                     D2D1::RectF (
                                                     upperLeftCorner.x,
                                                     upperLeftCorner.y,
                                                     (upperLeftCorner.x+size.width),
                                                   (upperLeftCorner.y + size.height)));                        _data.hr=_pRender->EndDraw ();
                          //passe la main à un autre agent dans le pipe même si
                          l'affichage ce passe mal
                          //l'agent suivant en sera informé
                          asend(_messageEnvoye,_data);
             }
             //Code omis pour plus de clarté
      }
      while(_data.Sentinel ==FALSE);
      done();
}

La méthode run() s’implémente de la même manière que les autres agents. Un message est reçu avec la bitmap Direct2D.
Pour l’afficher à l’écran on débute par la méthode BeginDraw() du contexte de rendu.
On dessine la bitmap via la méthode DrawBitmap() au milieu de l’écran.
Puis on finit toujours par la méthode EndDraw(). Cette dernière méthode retourne un HRESULT, qui permet de vérifier si une erreur est survenue.
L’affichage de tous les bitmaps, dans notre exemple, ce fait en tas, et nous sert de sablier nous indiquant qu’il y a un téléchargement encours.
Une fois qu’un bitmap est affiché, l’agent Display, le passe à l’agent suivant dans le pipeline. Enfin l’agent signale qu’il a fini son travail.

L’agent Store

L’agent store à juste comme rôle de sauvegarder les vignettes dans un vecteur, qui servira par la suite dans notre application pour le module d’animation.

Implémentation de l’agent Store

1.      L’agent store reçoit de l’agent Display un bitmap qu’il doit sauvegarder dans un vecteur
Son constructeur prendra alors comme paramètres :
Un point d’entrée pour envoyer un message. Vous noterez que le type envoyé sera un vecteur de RawData.
ITarget<concurrent_vector<RawData>>&messageEnvoye
Un point d’entrée pour recevoir le message de l’agent Bing :*
ISource<RawData>& messageRecu ;*

class DPEStoreAgent :public DPEBaseAgent
     
{
public:
      explicit DPEStoreAgent(ITarget<concurrent_vector<RawData>>& rawVector,                                                        ISource<RawData>& messageRecu);
      ~DPEStoreAgent(void);
protected :
      void run();
//Code omis pour plus de clarté
}

2.      La méthode run() s’implémente de la manière suivante :
void DPEStoreAgent::run()
{
            //Instanciation du vecteur
            concurrent_vector<RawData> vector;
      do
      {
             this->_data=receive(_messageRecu);
             //Code omis pour plus de clarté
             if (SUCCEEDED(_data.hr))
             {
                    //Stocke la donnée
                    vector.push_back (_data);             
             }
      }
      while(_data.Sentinel ==FALSE);
      //Une fois toutes les vignettes sauvegardées dans le vector
      //envoi le vecteur au prochain agent dans le pipeline
      asend(_messageEnvoye,vector);
      done();
}

Exécution du pipeline

Maintenant que nous avons vu comment implémenter le pipeline, nous allons voir comment le démarrer.
Tout d’abord, même si les agents du pipeline s’exécutent dans des threads différents que le thread qui a créé les fenêtres, il faut quand même en démarrer un nouveau pour éviter que l’interface graphique soit bloquée.

Pour ce faire, nous allons utiliser la notion de tâche, que nous retrouvons avec la librairie Parallèle de Visual Studio 2010.
Je ne vais pas rentrer dans les détails de cette librairie, vous retrouverez toutes les informations nécessaire dans l’aide de MSDN : https://msdn.microsoft.com/fr-fr/library/dd492418.aspx

Néanmoins, sachez que cette libraire, au même titre que la librairie Agent, simplifiera grandement le développement d’application parallèle. En effet, il sera préférable d’utiliser ce type de bibliothèque, plutôt que d’utiliser les mécanismes bas niveau type thread et autres objets de synchronisation de Windows. Mais ceci est un autre débat. Pour ceux qui souhaiteraient aller plus loin avec l’offre parallèle de Microsoft, n’hésitez pas à visiter le blog https://blogs.msdn.com/b/devpara/

Pour démarrer une nouvelle tâche voici une des manières de procéder.

1.      Nous allons créer une méthode RechercherImagesAsync 
void FenetreEnfant::RechercherImagesAsync(WebServiceConfiguration param)
qui prend comme paramètre la structure WebServiceConfiguration que nous passerons à l’agent BING.
Cette structure contient le texte de la requête, le nombre de vignettes à télécharger, ainsi que le numéro de page à utiliser en autre.
struct WebServiceConfiguration
{
      WCHAR Requete[1024];
      int NombreVignettes;
      int NumeroPage;
      int TotalPages;
      int TotalVignettes;
      BOOL NouvelleRequete;
      BOOL Sauvegarder;

};
Dans notre exemple, nous téléchargerons des images page par page , il est donc important de signifier à l’agent Bing qu’elle page nous voulons télécharger. (cf. étape 6 de la section utilisation de WWSAPI).

2.      Ensuite, il faut inclure l’entête ppl.h et utiliser l’espace de nom **Concurrency **:
#include <ppl.h>
using namespace ::Concurrency;

3.      Puis nous allons instancier et démarrer notre tâche
task_group *_tasks=nullptr;
_tasks=new task_group();
_tasks->run ([&]()
             {
                   RechercherImages (param);
             });

L’objet task_group, possède la méthode run(), qui permet de démarrer une tâche.
Comme vous pouvez le constater, nous lui passons en paramètre une syntaxe un peu particulière, sous formed’expression lambda. Cette forme provient d’une nouveauté du langage C++0X, et supporter par le compilateur C++ de Visual Studio 2010.
Mais avant d’aller plus loin et d’expliquer un peu les expressions lambda et leur utilité dans le langage C++, voyons ce que dit la documentation MSDN pour la méthode run().
template<typename _Function>
void run(const _Function& _Func);
template<typename _Function>
void run(task_handle<_Function>& _Task_handle);

Soit elle prend une fonction de type _Function définie dans l’entête TR1<functional> ou _Func peut être une lambda expression.
Soit elle prend un task_handle <_Function>, l’objet task_handle étant l’objet tâche défini dans l’entête <ppl.h>.
Sans les expressions lambda, voici comment nous aurions écrit notre code :
Nous aurions déclaré une fonction statique .
WebServiceConfiguration _param; //Déclaré en global
void FenetreEnfant::MaFonctionExterne()
      {
             RechercherImages (_param);
      }

Puis déclaré un appel à cette fonction de la manière suivante en utilisant la signature à base de task_handle :
void FenetreEnfant::RechercherImagesAsync(WebServiceConfiguration param)
{
//Sauvegarde des paramètres
_param=param;
task_handle<function<void (void)>> task(&FenetreEnfant::MaFonctionExterne);
_tasks->run(task);
_tasks->wait();
}

Syntaxe beaucoup moins lisible si il en faut qu’avec notre simple expression lambda.

Comme en mathématique, nous pouvons simplifier l’expression, en utilisant la signature à base de fonction.
function<void (void)> maFunc=&FenetreEnfant::MaFonctionExterne;
_tasks->run(maFunc);

C++0X réintroduit le mot clé auto qui permet de déduire un type par rapport à une expression, ce mot clé va nous permettre de simplifier encore notre expression
auto  maFunc=&FenetreEnfant:: MaFonctionExterne;
_tasks->run(maFunc);

Jusque la tout fonctionne correctement, néanmoins, le code n’est pas des plus lisible, car nous avons une variable globale déclaré en externe ainsi qu’une méthode déclarée ailleurs dans le code source.
C’est la ou intervient la lambda expression, afin de simplifier et améliorer la lisibilité du code.
Je peux réécrire mon expression comme ceci.
auto  maFunc=[&]()
             {
                    RechercherImages (param);
             });
_tasks->run(maFunc);

Pour arriver à la simplification ultime
_tasks->run ([&]()
             {
                    RechercherImages (param);
             });

C’est beau, court, concis et pratique et rend le code plus lisible. Ici plus besoin de déclarer des méthodes et des variables en globale.
Pour débuter une expression lambda on commence toujours par les signes crochets []
Si on souhaite capturer les paramètres par référence, on utilise le signe & (si on souhaite en faire une copie il faut les capturer par valeur en utilisant le signe =)
Dans notre exemple le compilateur saura qu’il faut passer une référence à la variable param
Ensuite on déclare le corps de la fonction de la même manière qu’un corps de fonction avec des parenthèses () et des accolades {} suivie d’un point virgule ;.
Etonnement, ce type de code compile avec le compilateur de VC ++ 2010.
[](){}();
[]{}();

Pour en savoir plus sur les expressions lambda : https://msdn.microsoft.com/fr-fr/library/dd293608.aspx

Maintenant, nous allons implémenter la méthode RechercheImage de la manière suivante.

1.      La signature sera la même que la méthode RechercherImagesAsync:
void FenetreEnfant::RechercherImages(WebServiceConfiguration Param)

2.      Ensuite nous définissons les messages qui voyageront entre chaque agent. Rappelez-vous pour définir un message nous allons utiliser la classe de messagerie unbounded_buffer<> avec notre type RawData qui sera véhiculé d’agent en agent.**
//Message qui contiendra les réponses du Service Bing**
unbounded_buffer <RawData> msgReponseBing; 
//Message qui contiendra les images sous forme de 
//tableau de BYTES téléchargées par l'agent HTTP

unbounded_buffer <RawData> msgTelechargeImages;                 
//Message qui contiendra les bitmaps Direct2D à afficher
unbounded_buffer <RawData> msgDisplayBitmap;
//Message qui contiendra les bitmaps à stocker
unbounded_buffer <RawData> msgStoreBitmap;
//Message qui contiendra le container de toutes les Images sous forme d’un vecteur
unbounded_buffer<concurrent_vector<RawData>> msgRawDataVecteur;

//Message qui nous serviront pour arrêter les 
//agents BING et http ces messages seront déclarés en globale

unbounded_buffer<BOOL> msgStopAgentBing;
unbounded_buffer<BOOL> msgStopAgentHttp;*

3.      Puis il faut instancier nos agents avec les bons messages et les bons paramètres
//Pointeur sur Helper Direct2D
DPEDirectD2D1helper *_pD2D1helper=nullptr;

DPEBingAgent  agentBing(msgReponseBing,msgStopAgentBing,Param);              
DPEHttpAgent  agentHttp(msgTelechargeImages, msgReponseBing,msgStopAgentHttp,FALSE);
//Récupère le contexte de rendu pour le fournir à l'agent WIC
ID2D1HwndRenderTarget *pRender=_pD2D1helper->GetContexteRendu ();
DPEWICAgent agentWIC(msgDisplayBitmap, msgTelechargeImages,FALSE,pRender);
DPEDisplayAgent agentDisplay(msgStoreBitmap,msgDisplayBitmap,pRender);
DPEStoreAgent agentStore(msgRawDataVecteur,msgStoreBitmap);

Vous noterez que le contexte de rendu est fourni par la méthode GetContextRendu(), implémenter dans la classe DPEDirectD2D1helper.

4.      On démarre les agents.
//démarrer les agents
agentBing.start ();                   
agentHttp.start ();
agentWIC.start ();                    
agentDisplay.start ();                                    
agentStore.start ();

Ici le démarrage se fait dans l’ordre des agents, mais en faite cela n’a pas d’importance dans notre pipeline, car tous les agents seront en attente sur la méthode receive() d’une 1er réponse de l’agent BING.

5.      Ensuite il faut attendre que tous les agents finissent leur travail (méthode done()).
//Attente des agents
agent::wait(&agentBing); 
agent::wait (&agentHttp);             
agent::wait(&agentWIC);  
agent::wait(&agentDisplay);
agent::wait(&agentStore);

6.      Vous aurez remarqué, qu’aucun agent n’est démarré, pour réceptionner le vecteur de RawData envoyé par l’agent Store. Peut importe, car nous pouvons utiliser la méthode receive() dans notre méthode sans passer par la structure d’un agent.
//contiendra le résultat de la recherche et de la 
//transformation en Bitmap 
concurrent_vector<RawData>  *_pconcurVector=nullptr;
_pconcurVector=new concurrent_vector<RawData>();

//le code bloque ici, tant qu'il n'a pas reçu le vecteur d'images
*_pconcurVector=receive(msgRawDataVecteur);

Remarque : Puisque le thread est bloqué sur la méthode receive(), il est superflue d’attendre que tous les agents finissent leur travail. L’étape 5 ici n’est pas nécessaire et est juste là à titre indicatif.

7.      Une fois que nous avons reçu notre vecteur de RawData, on en informe la procédure de la fenêtre à l’aide de l’API PostMessage()

PostMessage (_hwndEnfant,WM_COMMAND,WM_INIT_VIGNETTES,0);

Remarque : Nous utilisons la méthode PostMessage et non pas SendMessage, car notre pipeline s’exécute dans un thread différent que celui de l’IHM. Avec la méthode PostMessage, cela permet de poster un message dans la file des messages associée au thread qui à crée la fenêtre. En d’autres termes, nous synchronisons le thread de notre pipeline, avec le thread de l’IHM, et ceci parce qu’avec Windows nous ne pouvons pas accéder à un objet crée dans le thread principal à partir d’un autre thread.

Vous noterez que l’on passe le message WM_COMMAND et la commande utilisateur WM_INIT_VIGNETTE, pour initialiser les vignettes, afin que la plate-forme d’animation de Windows (que nous verrons à la section suivante Animation des vignettes.) puisse les animer.

Pour en finir avec notre pipeline et pour illustrer mes propos, vous apercevrez dans les figures suivantes (prise d’une photo à un instant T), que l’agent BING est en cours d’exécution, que l’agent HTPP est en attente d’un message. L’agent WIC quand à lui est train de décoder une image avant de l’envoyer à l’agent Display qui est en attente, alors que l’agent Store est en train de stocker une image que l’agent Display lui a envoyé.
Notre pipeline travaille ici sur 3 itérations en parallèle.

 

Saisie d’un mot clé et l’afficher avec DirectWrite
DirectWrite est une nouvelle API DirectX, qui permet aux applications de supporter un rendu texte de haute qualité. Elle supporte les fonctionnalités suivantes :

·        Indépendante du périphérique, DirectWrite améliore la lisibilité des documents (Text Layout)

·        Rendu des textes, en High-Quality, subpixel, et ClearType®, qui peuvent utiliser des techniques telles que le GDI, Direct2D, ou un rendu spécifique à l'application.

·        Un jeu d'API conforme à Unicode

·        Accélération Matériel du texte, lorsque DirectWrite est utilisé avec Direct2D

·        Formatage de texte multiple

·        Support de typographie avancée de type OpenType®

·        Compatible avec le GDI

Utilisation de DirectWrite

Pour utiliser les APIs DirectWrite, il faut procéder comme suit :

1.    Il faut inclure l’entête dwrite.h
#include <dwrite.h>

2.    Se lier à la librairie dwrite.lib

3.    Créer une fabrique DirectWrite
IDWriteFactory *_pfabriqueDWrite;
hr=DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
                         __uuidof(IDWriteFactory),
                         reinterpret_cast<IUnknown**>(&_pfabriqueDWrite));

4.    Créer un format de texte
IDWriteTextFormat *format;
_pfabriqueDWrite->CreateTextFormat (L"Arial", NULL,
               DWRITE_FONT_WEIGHT_REGULAR, 
               DWRITE_FONT_STYLE_NORMAL,
               DWRITE_FONT_STRETCH_NORMAL,
               20.0f,
               L"fr-fr",
            &format);

5.    Utiliser un contexte de rendu Direct2D et sa méthode DrawText, pour écrire du texte à l’écran.
void DPEDirectD2D1helper::EcrireMotCle(LPCWSTR texte)
{
     //Code omis pour plus de clarté
     size_t length=wcslen (texte);
     //Code omis pour plus de clarté
     FLOAT fontSize=_ptextFormat->GetFontSize ();
     D2D1_SIZE_F sizeRendu= _pcontexteRenduD2D1->GetSize (); 
     _pcontexteRenduD2D1->BeginDraw ();
     D2D1_RECT_F layoutRect1 = D2D1::RectF(0,sizeRendu.height/4 ,sizeRendu.width,fontSize+sizeRendu.height/4);
     _pcontexteRenduD2D1->FillRectangle (layoutRect1,_pBackgroundBrush);
     _pcontexteRenduD2D1->DrawTextW (texte,static_cast<UINT>(length),
                               _ptextFormat,
                                layoutRect1,
                                _pbrosseBlanche);
     _pcontexteRenduD2D1->EndDraw();

}
Le code DirectWrite se trouve dans le fichier DPEDirect2D1Helper.cpp

6.    Vous noterez que la méthode EcrireMotCle() prend un pointeur sur un WCHAR, pointeur qui provient de la méthode OnWMChar de la fenêtre enfant, et que je ne détaillerai pas ici.
HRESULT FenetreEnfant::OnWMChar(WPARAM wChar)

Animation des vignettes

Le gestionnaire d'Animation de Windows (Windows Animation) permet une animation riche des éléments d'interface utilisateur. Il est conçu pour simplifier le processus d'ajout d'animation à l'interface utilisateur d'une application et pour permettre aux développeurs de mettre en œuvre des animations qui sont fluides, naturelles et interactives.

La plate-forme d’animation de Windows 7, gère la planification et l’exécution des animations, et fournit également une bibliothèque de fonctions mathématiques utiles pour spécifier le comportement d'un élément d'interface utilisateur au fil du temps. Elle permet également aux développeurs de mettre en œuvre des fonctions personnalisées qui fournissent des comportements supplémentaires.

Il est à noter que, ce n’est pas elle qui est en charge du rendu, mais qu’elle peut être utilisée avec n'importe quelle plate-forme graphiques, y compris Direct2D, Direct3D ou GDI +.

Les deux unités fondamentales d'une animation sont (1) la caractéristique d'un élément visuel à animer et (2) la description de la façon dont cette caractéristique change au fil du temps. Une application peut animer une grande variété de caractéristiques telles que la position, la couleur, taille, rotation, contraste et opacité.

Dans Windows Animation, une variable d'animation représente la caractéristique à animer. Une transition décrit l'évolution de la valeur de cette variable d'animation lorsqu’une animation se produit. Par exemple, un élément visuel pourrait avoir une variable d'animation qui spécifie son opacité, et une action de l'utilisateur pourrait générer une transition qui prend cette opacité d'une valeur de 50 à 100, ce qui représente une animation de semi-transparent entièrement opaque. Un storyboard est un ensemble de transitions appliquée à une ou plusieurs variables d'animation au fil du temps.

La plate-forme d’animation de Windows est composée :

·        D’un gestionnaire d’animation, qui permet de créer des variables, des storyboards et de planifier les animations.

·        Des variables à animer qui représente un des aspects d’un élément visuel à animer. Dans notre exemple nous animerons les coordonnées des images.

·        Un système de timer, qui assure que les animations utiliseront une cadence de rafraichissement fluide et consistante, tout en réduisant l’utilisation de ressources systèmes lors du rendu lorsque le système est fortement en charge.

Diagramme lorsque l’application interagit directement avec la plate-forme d’animation.

Diagramme lorsque l’application interagit directement avec la plate-forme d’animation.

 

Pour de plus amples information sur Windows Animation https://msdn.microsoft.com/fr-fr/library/dd371981(v=VS.85).aspx

Utilisation de Windows Animation

Dans notre exemple, ce que nous voulons, c’est créer un effet d’animation à la fin d’exécution de notre pipeline, et lorsque la fenêtre se repeint (WM_PAINT). L’animation sera planifiée à partir d’un storyBoard et utilisera des effets de transitions type parabolique et des effets d’accélération et de décélération.

Pour mettre en place une animation voici les différentes tâches à accomplir.

1.      Tout d’abord il faut inclure l’entête UIAnimation.h
#include <UIAnimation.h>

2.      Une animation débute toujours par la création du gestionnaire d’animation ainsi qu’un certain nombre d’autres objets comme le timer, la librairie de transitions et un gestionnaire qui reçoit l’état du gestionnaire d’animation.
// Création du gestionnaire d'Animation***
IUIAnimationManager *_pAnimationManager;
    HRESULT hr = CoCreateInstance(
        CLSID_UIAnimationManager,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&_pAnimationManager));
// Création du Timer d'animation
IUIAnimationTimer *_pAnimationTimer;
*

hr = CoCreateInstance(
            CLSID_UIAnimationTimer,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&_pAnimationTimer));

// Création de la librairie de Transition
IUIAnimationTransitionLibrary *_pTransitionLibrary

hr = CoCreateInstance(
      CLSID_UIAnimationTransitionLibrary,
      NULL,
      CLSCTX_INPROC_SERVER,
      IID_PPV_ARGS(&_pTransitionLibrary));*

//Permet de recevoir l'état du gestionnaire d'animation
IUIAnimationManagerEventHandler *pManagerEventHandler;
hr = CManagerEventHandler::CreateInstance(hWnd,&pManagerEventHandler);
//Fournit le gestionnaire d'état au gestionnaire d'animation
hr = _pAnimationManager->SetManagerEventHandler(pManagerEventHandler);

Nous créons un objet **IUIAnimationTimer **, car nous en aurons besoins pour planifier un storyboard et mettre à jour le gestionnaire d’animation.
Nous créons un objet IUIAnimationTransitionLibrary, qui nous permettra d’affecter une transition à un StoryBoard. Plusieurs types de transition sont disponibles, telles que des transitions cubique, linéaire, parabolique, d’accélération et de décélération, etc. Pour de plus amples informations sur les storyBoard et les transitions, reportez vous à l’aide MSDN : https://msdn.microsoft.com/fr-fr/library/dd756779(v=VS.85).aspx
Puis nous créons un objet de type IUIAnimationManagerEventHandler, qui aura en charge de recevoir l’état du gestionnaire d’animation. Toute la mécanique COM d’implémentation de ce gestionnaire est contenu dans les fichiers ManagerEventHandler.h et UIAnimationHelper.h, provenant des exemples du SDK Windows 7.

3.      Ensuite il faut créer les variables à animer.
IUIAnimationVariable *_pAnimationVariableX;
IUIAnimationVariable *_pAnimationVariableY;

 
HRESULT CThumbnail::Initialize(
    ID2D1Bitmap *pBitmap,
    IUIAnimationManager *pAnimationManager,
    DOUBLE x,
    DOUBLE y))
{
// Code omis pour plus de clarté

*// création des variables d'animations pour les coordonnées X et Y
HRESULT hr = pAnimationManager->CreateAnimationVariable(x,&_pAnimationVariableX);
hr = pAnimationManager->CreateAnimationVariable(y,&_pAnimationVariableY) ;
// Code omis pour plus de clarté
}  *  
Nous utilisons le gestionnaire d’animation pour créer les variables d’animation en fonction des positions x et y d’une vignette à l’écran. Vous retrouverez le code complet dans la classe CThumnnail fourni avec cet article et qui est issue également d’un exemple du SDK Windows 7.

4.      Ensuite il faut créer un storyboard
// Création du storyboard pour la transition de toutes les vignettes
    IUIAnimationStoryboard *pStoryboard;
    HRESULT hr = _pAnimationManager->CreateStoryboard(
        &pStoryboard
        );

5.      Puis il faut ajouter les transitions au storyboard.
HRESULT CLayoutManager::AddThumbnailTransitions(
    IUIAnimationStoryboard *pStoryboard,
    IUIAnimationVariable *pVariablePrimary,
    DOUBLE valuePrimary,
    IUIAnimationVariable *pVariableSecondary,
    DOUBLE valueSecondary
    )
{
// Code omis pour plus de clarté
IUIAnimationTransition *pTransitionPrimary;

    HRESULT hr = _pTransitionLibrary->CreateParabolicTransitionFromAcceleration(
        valuePrimary,
        0.0,
        ACCELERATION,
        &pTransitionPrimary);

* hr = pStoryboard->AddTransition(
              pVariablePrimary,
              pTransitionPrimary);
// Code omis pour plus de clarté
UI_ANIMATION_KEYFRAME keyframeEnd;
hr = pStoryboard->AddKeyframeAfterTransition(pTransitionPrimary,
                  &keyframeEnd);
// Code omis pour plus de clarté
IUIAnimationTransition *pTransitionSecondary;

hr = _pTransitionLibrary->CreateAccelerateDecelerateTransition(1.0,
                   valueSecondary,ACCELERATION_RATIO,DECELERATION_RATIO,    
                      &pTransitionSecondary );
// Code omis pour plus de clarté
hr = pStoryboard->AddTransitionBetweenKeyframes(
                        pVariableSecondary,
                        pTransitionSecondary,
                        UI_ANIMATION_KEYFRAME_STORYBOARD_START,
                        keyframeEnd);
// Code omis pour plus de clarté
}*

Vous noterez que nous utilisons la notion de KeyFrame, qui permet de spécifier le début et la fin d’une transition. Notez également que nous passons en paramètre de la fonction les variables IUIAnimationVariable, variables qui determinent la position x et y d’une vignette, afin de les utiliser dans les transitions.

6.      Ensuite il faut planifier le storyboard.
 // Récupère le temps courant est planifie le storyboard
UI_ANIMATION_SECONDS timeNow;
hr = _pAnimationTimer->GetTime(&timeNow);
hr = pStoryboard->Schedule(timeNow);
                }

Le code complet des étapes 4 à 6 se trouve dans le fichier LayoutManager.cpp, issue lui aussi du SDK de Windows 7.

7.      Une fois que le storyboard est planifié, cela déclenche une modification de l’état du gestionnaire d’animation qui le notifie au gestionnaire d’état **IUIAnimationManagerEventHandler (**que nous avons crée à l’étape 2) en appelant sa méthode OnManagerStatusChanged

IFACEMETHODIMP OnManagerStatusChanged(UI_ANIMATION_MANAGER_STATUS newStatus,UI_ANIMATION_MANAGER_STATUS previousStatus)
    {
        HRESULT hr = S_OK;
 
        if (newStatus == UI_ANIMATION_MANAGER_BUSY)
        {
            //Repeindre la fenêtre
             BOOL bResult = InvalidateRect(_hWnd,
                                           NULL,
                                           FALSE);
}

Comme le gestionnaire d’animation est occupé à ce moment précis, cela déclenche l’appel à l’API Windows InvalidateRect(), indiquant à la fenêtre de se redessiner.
Le code de cette méthode se trouve dans le fichier ManagerEventHandler.h

8.      Le faite d’invalider la zone cliente de la fenêtre enfant, déclenche le message Windows WM_PAINT, dans lequel nous indiquons qu’il faut dessiner les vignettes.

9.      Avant de dessiner les vignettes, nous devons mettre à jour l’état  du gestionnaire d’animation.
UI_ANIMATION_SECONDS secondsNow;
hr = _pAnimationTimer->GetTime(&secondsNow);
hr = _pAnimationManager->Update(secondsNow);

10.   Puis lire les variables d’animations, afin de retrouver les nouvelles coordonnées de la vignette.
DOUBLE x = 0;
HRESULT hr = _pAnimationVariableX->GetValue(&x);
DOUBLE y = 0;
hr = _pAnimationVariableY->GetValue(&y);

11.   Une fois les nouvelles coordonnées obtenues, nous pouvons dessiner la vignette. Nous utiliserons ici notre contexte Direct2D.

ID2D1Bitmap *_pBitmap;
D2D1_SIZE_F size = _pBitmap->GetSize();
D2D1_RECT_F rc = D2D1::Rect<FLOAT>(
                static_cast<FLOAT>(x - 0.5 * size.width),
                static_cast<FLOAT>(y - 0.5 * size.height),
                static_cast<FLOAT>(x + 0.5 * size.width),
                static_cast<FLOAT>(y + 0.5 * size.height) 
                );
pRender->BeginDraw();
pRender->DrawBitmap(_pBitmap,rc,OPACITY);
pRender->EndDraw();

Le code des étapes 9 à 11 se trouve dans lefichier DPEAnimationHelper.cpp ainsi que dans le fichier thumbnail.cpp.

Test de la solution.

Enfin nous arrivons au bout de nos peines, il temps maintenant d’exécuter l’application AfficherImageBing.exe.
Une fenêtre sur fond noir s’affiche, WOUAW !!

Test de la solution

 

Tapez un mot clé, par exemple Windows 7

Tapez un mot clé, par exemple Windows 7

 

Puis la touche <Entrez>

Les images commencent à se charger. (Evidemment, ça aurait tout aussi bien marché si vous aviez tapé Kinect, Pamela Anderson ou voiture).Vous remarquerez que leur affichage se fait au milieu de l’écran, c’est ce que nous avions prévu dans l’agent Display.

Les images commencent à se charger

 

Une fois que toutes les images sont téléchargées, décodées, et stockées, l’animation commence.

L'animation commence
L'animation commence (2)
En retaillant la fenêtre, vous devriez voir des effets de mouvement d’images paraboliques.

Vous pouvez également naviguer dans les pages d’images à l’aide des touches flèche gauche et flèche droite. En appuyant sur la flèche droite s’il n’y a pas de page dans le cache, l’application en télécharge une nouvelle.

Si vous souhaitez modifier le nombre de vignette par page, ouvrez le fichier AfficherImageBing.config et modifiez la valeur de la clé NombreVignettes

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key ="NombreVignettes" value ="50"/>
  </appSettings>
</configuration>

Il est également possible de sauvegarder une image complète dans le répertoire courant de l’application, en cliquant sur le bouton droit de la souris au niveau d’une image.

Sauvegarder une image complète

 

Conclusion

Dans cet article nous avons vu, comment :

développer une application Win32 en C++ avec une interface réactive qui ne se bloque pas lorsqu’un téléchargement est en cours, et comment utiliser les technologies suivantes :

·        Les librairies agent et parallèle de Visual Studio 2010 en lieu et place des threads.

·        Les services de Microsoft Bing avec les API Windows Web Service API afin de télécharger les adresses électroniques des images.

·        L’API WinInet pour télécharger les données brutes des images.

·        Windows Imaging Component pour décoder les images et en créer des bitmaps Direct2D

·        Direct2D et DirectWrite pour afficher les images et du texte à l’écran en s’appuyant sur l’accélération matériel.

·        Et la plate-forme d’animation de Windows, pour un rendu fluide des images à l’écran avec des effets de transitions.

Maintenant que vous avez le code source, rien ne vous empêche d’améliorer l’application, en ajoutant par exemple un module qui permettrait d’afficher sous forme de diaporama, cette fois-ci, non plus les vignettes, mais les images complètes. Ce tutorial vous donnera peut-être l'envie, qui sait, de vous attaquer également à d'autres services Web comme Bing Maps ou les web services Google.

Eric Vernié

 

Article Powered by Windows Seven and Windows Live