Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

WinRT : Prendre des photos

[new:30/11/2012]L’avantage d’un OS PC conçu pour marcher aussi sur tablettes et smartphones est qu’il est obligé de fournir les fonctions qu’on attend de ces systèmes, comme prendre des vidéos ou des photos. Windows 8 se plie donc à l’exercice là où d’excellents OS comme Windows 7 ou même XP ne proposaient que du bas niveau. Voyons comment en tirer partie…

Principes

WinRT propose une API riche et complexe, bien plus grosse que .NET complet qui était déjà plutôt “joufflu” ! Bien entendu cette prise de poids de l’API n’est pas un hasard ni un manque de maîtrise de la part de Microsoft. Au contraire, et on le voit bien à la vitesse Windows 8, cet OS est conçu pour aller vite, même sur des machines modestes.

L’une des raisons de ce gonflement de l’API est plutôt lié à toutes les choses que Windows ignorait superbement et qui, aujourd’hui, font partie du minimum syndical même sur un smartphone… géolocalisation, partage, vidéo, photo, et bien d’autres choses doivent être gérées proprement par l’OS et non plus laissées au bon vouloir de chaque logiciel et des implémentations de niveau divers que cela suppose (incompatibles entre elles de plus).

Windows 8, le fédérateur, se devait dans sa partie WinRT, celle qui est justement portable sur tous les form factors, d’intégrer l’ensemble des API permettant à toute application d’offrir des services riches, identiques et compatibles.

De fait, l’API dédiée à la gestion des médias dans WinRT est très riche et prend en considération de nombreux paramètres.

La version simple

Dans sa version la plus simple l’API photo de WinRT se résume à quelques appels rudimentaires. Voici un squelette de code n’utilisant que le strict nécessaire :

private async void CapturePicture()
{
    var dialog = new CameraCaptureUI();
    var file = await dialog.CaptureFileAsync(CameraCaptureUIMode.Photo);
    if (file != null)
    {
    // utiliser le fichier …
    }
}

 

Autoriser le crop

Bien que simple, cette approche peut être facilement agrémentée d’options, comme autoriser l’utilisateur à cropper l’image capturée :

private async void CapturePicture()
{
    var dialog = new CameraCaptureUI() 
                   { PhotoSettings.AllowCropping = true };
    var file = await dialog.CaptureFileAsync(CameraCaptureUIMode.Photo);
    if (file != null)
    {
    // utiliser le fichier...
    }
}

 

Comme on le voit l’accès aux paramètres est assez direct notamment par le biais de la propriété PhotoSettings dont le nom est explicite. De même la nature de la capture se fixe lors de l’appel à CaptureFileAsync. Ci-dessus nous indiquons un mode de capture Photo. Le même mécanisme permet de demander la capture d’un flux audio ou d’une vidéo.

Bien que sophistiqué WinRT reste compréhensible.

Une application de capture

Pour illustrer les mécanismes expliqués plus haut et quelques autres, le mieux est de créer une application Windows Store..

Je vais vous montrer comment énumérer toutes les devices gérant des médias, comment utiliser le File Picker pour sélectionner des photos et comment, bien entendu, prendre une photo.

L’application part d’un template “Blank” pour Windows Store. On peut diviser visuellement l’application en trois partie verticales, à droite la liste des devices, au centre le système de capture, à droite l’utilisation du File Picker. Je dis ça pour ceux qui joueront avec le code (disponible en fin de billet).

Autoriser l’accès à la webcam

Sécurité oblige, si une application veut utiliser la webcam elle doit le déclarer dans son manifeste. Et l’utilisateur sera tenu au courant et devra valider cette autorisation.

image

N’oubliez pas ce petit détail si vous ne voulez pas chercher inutilement un bug qui n’existe pas…

Au premier lancement de l’application, l’utilisateur verra apparaître un message de confirmation pour l’accès à la webcam :

image

Si l’utilisateur refuse l’autorisation il faudra veiller à le tester pour éviter un plantage (certains se diront peut-être qu’à ce stade, vu le peu de confiance et d’intérêt que marque l’utilisateur, autant laisser l’application se planter, hein… ce qui n’est pas jamais un bon calcul. Même éconduit, restez polis, parfois cela ne sert à rien, mais parfois cela fait revenir les gens sur leur décision !).

Lister les devices

L’affichage est effectué par une simple ListBox dont le contenu est créé au lancement de l’application. La méthode “getAllDevices()” est appelée dans le constructeur de la page :

 private async void getAllMedia()
        {
            try
            {
                devices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
                if (devices == null || devices.Count == 0)
                {
                    await new 
                      Windows.UI.Popups.MessageDialog("Aucune Webcam trouvée. Désolé.").ShowAsync();
                }
                else
                {
                    foreach (var device in devices)
                    {
                        lstDevices.Items.Add(device.Name);
                    }
                    lstDevices.SelectedIndex = 0;
                }
            } catch(Exception e)
            {
                new Windows.UI.Popups.MessageDialog(e.Message).ShowAsync();
            }
        }

 

Dans ce code totalement asynchrone (mais qui grâce à async/await s’écrit presque comme un code synchrone) j’énumère l’ensemble des périphériques ayant la possibilité de prendre des photos ou de capturer des flux vidéo. Il n’y a en fait que l’appel à DeviceInformation.FindAllAsync qui est intéressant. Le reste est de l’habillage pour gérer les éventuelles exceptions et pour remplir la ListBox avec les noms des périphériques trouvés.

image

Sur cette machine j’ai une WebCam Trust, on la voit apparaître dans la liste…

L’intérêt d’énumérer les périphériques est essentiel dans des conditions où plusieurs de ceux-ci sont présents pour une même fonction. Supposons une tablette Surface possédant deux capteurs vidéos (frontal et arrière), il est important de laisser le choix du capteur à utiliser à l’utilisateur. Mais sur un PC il peut se faire qu’il n’y ait aucune webcam, il faut le gérer aussi.

De même un microphone de bonne qualité peut être connecté à la machine et l’utilisateur préfèrera peut-être utiliser ce dernier pour enregistrer un flux audio que le microphone intégré à sa webcam.

Dans toutes ces circonstances détecter les périphériques assurant certaines fonctions (comme ici la vidéo) peut faire la différence entre un bon logiciel bien fini et un soft bâclé…

Dans l’application exemple, cette liste ne sert à rien d’autre qu’à la démonstration. Dans une application réelle elle permettrait donc de choisir le périphérique le mieux adapté ou à laisser l’utilisateur faire ce choix.

Utiliser le File Picker

Le File Picker est un outil puissant, bien plus complet et versatile que les dialogues d’ouverture de fichier des versions précédentes de Windows. De plus son UI a été entièrement pensée pour s’adapter au mode Full Screen de Metro et au tactile.

Je suis navré pour le choix des images, je n’avais que ma trombine à prendre en photo, je vous aurais bien mis des clichés de jeunes et jolies demoiselles dénudées pour maintenir en éveil votre intérêt, mais je n’ai pas de nymphettes qui dansent nues dans mon bureau prêtes à être photographiées. Sachez que je suis le premier à le regretter sincèrement, mais il faudra vous satisfaire de ma barbe de trois semaines !

image

ci-dessus une capture dans le simulateur du File Picker en action. De nombreuses options sont disponibles à l’utilisateur, la sélection est simple et matérialisée de façon homogène avec l’ensemble de l’OS (les photos sélectionnées sont entourées d’un rectangle de couleur et le coin supérieur droit est marqué).

L’invocation du File Picker est largement paramétrable. Ici j’ai utilisé le ViewMode pour forcer un affichage de type Thumbnail, on aurait pu choisir une simple liste. De même j’ai initialisé SuggestedStartLocation pour que le dialogue s’ouvre sur un répertoire particulier (ici la librairie d’images de l’utilisateur en cours). Enfin, en précisant des FileTypeFilter j’ai limité l’affichage aux fichiers “jpeg" et “png”.

Une fois la sélection effectuée par l’utilisateur je remplie une liste qui contient des URL que je fabrique au passage. Le tout est affiché par une ListView possédant un petit ItemTemplate qui utilise un composant Image et un peu de binding pour afficher l’image :

image

On voit ci-dessus les images sélectionnées apparaître dans la ListView templatée (ainsi que le bouton “File Picker” permettant de tester la fonction).

Vous noterez que si vous n’avez pas besoin d’une sélection multiple il est préférable d’utiliser PickSingleFileAsync au lieu de PickMultipleFileAsync comme je le fais dans cette démo. C’est une évidence mais c’est mieux de le dire !

Le File Picker est simple à utiliser, le code ci-dessous le montre. Simple mais pas forcément évident. En effet, lorsque l’utilisateur valide sa sélection on ne reçoit bien entendu pas une liste d’objets bitmap… Mais à la place notre code reçoit une IReadOnlyList de StorageFile.

Pour afficher (ou utiliser autrement) la ou les images retournées on peut obtenir un Stream sur chaque fichier et l’exploiter à sa guise (pour le transférer dans un bitmap qu’on affichera, pour le copier ou n’importe quoi d’autre).

Ici j’en profite pour vous faire voir une autre possibilité : copier dans l’espace local de l’application les images ce qui nous permet ensuite d’utiliser des URL directes pour les afficher. Ces URL sont construites de la façon suivante “ms-appdata:///Local/filename” où “filename” est le nom du fichier.

L’intérêt est d’en profiter pour parler de cette URL et de la façon de la construire et aussi de montrer la fonction de copie de fichiers.

Le code du clic sur le bouton File Picker est le suivant :

private async void btnFilePicker_Click(object sender, RoutedEventArgs e)
{
     var openPicker = new FileOpenPicker
                   {
                       ViewMode = PickerViewMode.Thumbnail,
                        SuggestedStartLocation = PickerLocationId.PicturesLibrary
                    };
     openPicker.FileTypeFilter.Add(".jpg");
     openPicker.FileTypeFilter.Add(".jpeg");
     openPicker.FileTypeFilter.Add(".png");
     var files = await openPicker.PickMultipleFilesAsync(); 
      if (files.Count == 0)
            {
                await new Windows.UI.Popups.MessageDialog("Aucun fichier sélectionné !").ShowAsync();
                return;
            }

     var fileStorage = Windows.Storage.ApplicationData.Current.LocalFolder;
      foreach (var file in files)
            {
                var copiedFile = await file.CopyAsync(fileStorage, file.Name, 
                                                               NameCollisionOption.GenerateUniqueName);
                lstImages.Items.Add(string.Format("ms-appdata:///Local/{0}", copiedFile.Name));
            }
 }

 

Rien de bien sorcier donc.

La ListView est templatée pour afficher l’image à partir de l’URL fabriquée par le code ci-dessus, le template est minimaliste :

<ListView x:Name="lstImages">
   <ListView.ItemTemplate>
       <DataTemplate>
           <Image Source="{Binding}" Stretch="UniformToFill"/>
       </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Prendre une photo

Il faut bien y venir, ce coup-ci ma barbe est incontournable et plein cadre ! J’ai agrémenté les clichés d’un Nokia sous Windows Phone pour que ça fasse plus gai… (dans l’autre main c’est un Samsung SIII, et pour les curieux le truc rond derrière ce n’est pas une assiette de jongleur sur un bâton mais l’une des cymbales de ma batterie électronique, à cette place c’est la Ride pour les connaisseurs…).

image

Une fois abstraction faite du modèle qui a posé pour le cliché, on remarque que WinRT propose de nombreuses choses à l’utilisateur comme de revenir en arrière (annulation de la prise de vue), un rectangle de crop pour recadrer le cliché, et deux boutons, l’un pour valider, l’autre pour refaire le cliché.

C’est très complet. Mais certaines fonctions sont paramétrables. Le crop par exemple n’est là que parce que le code l’a demandé. D’autres comme le timer sont proposées par défaut, ce qui est bien pratique lorsqu’on doit par exemple faire l’imbécile avec un téléphone dans chaque main pour un cliché d’illustration d’une démo sur son blog (comment j’aurai pu déclencher la prise de vue sinon ?…).

Une fois validé, le cliché apparait dans l’application (avec le crop appliqué) :

image

Le code qui a servi a immortaliser cette scène est le suivant :

private async void btnCapture_Click(object sender, RoutedEventArgs e)
{
    try
    {
       var dialog = new CameraCaptureUI();
       dialog.PhotoSettings.CroppedAspectRatio = new Size(1, 1);
       dialog.PhotoSettings.Format = CameraCaptureUIPhotoFormat.Png;

       var file = await dialog.CaptureFileAsync(CameraCaptureUIMode.Photo);
       if (file != null)
          {
              var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
              var bmp = new BitmapImage();
              bmp.SetSource(stream);
              imgViewer.Source = bmp;
              stream.Dispose();
          }
     } catch(Exception ee)
          {
            new Windows.UI.Popups.MessageDialog(ee.Message).ShowAsync();
          }
}

 

Je n’insisterai pas sur la nature asynchrone de tout ce code, sur le rôle de async et await, mais cela ne veut pas dire qu’il n’y a rien à voir !

le mécanisme de la prise de vue est assez simple à comprendre : CaptureFileAsync effectue la capture et retourne le fichier qui a été créé. Il suffit de créer un Stream pour alimenter un Bitmap avec le contenu du fichier puis d’utiliser ce dernier comme source de l’objet Image de l’UI pour faire apparaître la photo.

Conclusion

La prise de vue sur un PC est un gadget parfois utile, mais sur une tablette ou un smartphone cela devient une fonction incontournable riche en possibilités ('interprétation de code barre, de QR code, détection de mouvement, etc).

Les API mises à disposition par WinRT sont assez simples mais très complètes. Il faut toutefois se méfier de l’asynchronisme et écrire son code en en tenant compte…

Ce billet ne montre qu’une partie des possibilités, prendre des videos, enregistrer uniquement du son peuvent s’avérer tout aussi utile. Néanmoins les mécanismes restent les mêmes et le lecteur intéressés possèdera les clés pour aller fouiller la documentation Microsoft !

Bonne prise de vue !

Stay Tuned !

Projet VS 2012 / Windows 8 : WinRTPhotoCapture

blog comments powered by Disqus