Работа с веб-камерой в WinRT

111

Вы уже видели, как приложения Windows Runtime создают объекты WriteableBitmap «с нуля» или загружают существующие файлы растровой графики. Существуют и другие способы получения растровых изображений в программах. Например, позже будет показано, как программы могут получать изображения от других приложений - напрямую или через буфер обмена.

Получение фотографий с камеры

Кроме того, приложение может получить изображение со встроенной камеры. Существует два основных решения этой задачи; если вы готовы передать управление Windows 8, чтобы операционная система могла вывести свой обычный интерфейс камеры, процесс получается исключительно простым.

Использование камеры необходимо явно разрешить в файле Package.appxmanifest. В Visual Studio откройте этот файл, перейдите на вкладку Capabilities и установите флажок Webcam. Я проделал все это в программе EasyCameraCapture. Файл MainPage. xaml выглядит так:

<Page ...>

    <Grid Background="#FF1D1D1D">
        <Image Name="image" />
        
        <Button Content="Сделать фото!"
                FontSize="48"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Click="OnButtonClick" />
    </Grid>
</Page>

Обработчик события Click кнопки создает экземпляр класса CameraCaptureUI, определенного в пространстве имен Windows.Media.Capture, и вызывает метод CaptureFileAsync:

using System;
using Windows.Media.Capture;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        private async void OnButtonClick(object sender, RoutedEventArgs e)
        {
            CameraCaptureUI cameraCap = new CameraCaptureUI();

            cameraCap.PhotoSettings.MaxResolution = CameraCaptureUIMaxPhotoResolution.VerySmallQvga;

            StorageFile storageFile = await cameraCap.CaptureFileAsync(CameraCaptureUIMode.Photo);

            if (storageFile != null)
            {
                IRandomAccessStreamWithContentType str = await storageFile.OpenReadAsync();
                BitmapImage bitmap = new BitmapImage();
                await bitmap.SetSourceAsync(str);
                image.Source = bitmap;
            }
        }
    }
}

До вызова CaptureFileAsync программа может задать различные свойства CameraCaptureUI для выбора формата файла и размера в пикселах, включения обрезки и т.д.

Когда в вашем приложении будет вызван метод CaptureFileAsync, Windows 8 переключается на экран, очень похожий на обычное приложение "Windows 8 Камера". Отличий совсем немного: кнопка "Режим видео" заблокирована (но блокировку можно снять, передав методу CaptureFileAsync значение CameraCaptureUIMode.PhotoOrVideo), а в левом верхнем углу отображается круглая кнопка со стрелкой влево.

Чтобы вернуться к приложению EasyCameraCapture, нажмите круглую кнопку со стрелкой; в этом случае возвращаемый объект StorageFile равен null. Также можно сохранить изображение, прикоснувшись или щелкнув на экране, а затем нажав нижнюю кнопку с «галочкой».

При возвращении в программу объект StorageFile ссылается на файл, хранящийся в каталоге TempState локального хранилища приложения. Код EasyCameraCapture просто отображает содержимое файла.

Ваше приложение может передать управление FileSavePicker, чтобы сохранить изображение под контролем пользователя или же сохранить изображение автоматически где-то в библиотеке Pictures. Возможно, ваше приложение особым образом обрабатывает сохраненные изображения, и для них будет удобно выделить специальный каталог в библиотеке Pictures. (Стандартное приложение Windows 8 Камера сохраняет фотографии в подкаталоге Camera Roll каталога Pictures.) Для этого необходимо включить доступ к библиотеке Pictures в настройках приложения, как это делает стандартное приложение Windows 8 Камера.

Вы также можете опуститься на более низкий уровень интерфейса камеры и фактически написать собственное полноценное приложение с предварительным просмотром, выбором камеры (если их несколько), запуском съемки и т.д.

Основы этого процесса представлены в проекте HarderCameraCapture. Файл XAML содержит то, что вы еще не видели — элемент CaptureElement для предварительного просмотра видео, а также хорошо знакомый элемент Image:

<Page ...>

    <Grid Background="#FF1D1D1D">
        <CaptureElement Name="captureElement" />
        <Image Name="image" />
    </Grid>
</Page>

Файл фонового кода использует обработчик Loaded для выполнения инициализации. Статический метод DeviceInformation.FindAllAsync() позволяет получить коллекцию устройств видеозахвата. Объект DeviceInformation содержит строковый идентификатор и свойство EnclosureLocation, по которому программа может определить местонахождение камеры. Следующий код пытается найти камеру на передней панели, но если ему это сделать не удается, он выбирает первую (и возможно, единственную) камеру в коллекции:

using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Devices.Enumeration;
using Windows.Graphics.Imaging;
using Windows.Media.Capture;
using Windows.Media.MediaProperties;
using Windows.Storage.Streams;
using Windows.UI;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media.Imaging;

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        MediaCapture mediaCapture = new MediaCapture();

        public MainPage()
        {
            this.InitializeComponent();
            Loaded += OnMainPageLoaded;
        }

        private async void OnMainPageLoaded(object sender, RoutedEventArgs e)
        {
            DeviceInformationCollection devInfos = 
                await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);

            if (devInfos.Count == 0)
            {
                await new MessageDialog("Не обнаружено устройств для записи видео.").ShowAsync();
                return;
            }

            string id = null;

            // Попытка найти камеру на передней панели
            foreach (DeviceInformation devInfo in devInfos)
            {
                if (devInfo.EnclosureLocation != null &&
                        devInfo.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front)
                    id = devInfo.Id;
            }

            // Если камера недоступна, просто выбираем первую камеру
            if (id == null)
                id = devInfos[0].Id;

            // Создание настроек инициализации
            MediaCaptureInitializationSettings settings = new MediaCaptureInitializationSettings();
            settings.VideoDeviceId = id;
            settings.StreamingCaptureMode = StreamingCaptureMode.Video;

            // Инициализация устройства MediaCapture
            await mediaCapture.InitializeAsync(settings);

            // Связывание с CaptureElement
            captureElement.Source = mediaCapture;

            // Запуск предварительного просмотра
            await mediaCapture.StartPreviewAsync();
        }

        // ...
    }
}

После получения идентификатора устройства, обработчик Loaded создает объект MediaCaptureInitializationSettings и использует его для инициализации объекта MediaCapture, определенного в формате поля. Объект MediaCapture назначается источником данных элемента CaptureElement, экземпляр которого создается в файле XAML.

В конце обработчика Loaded режим предварительного просмотра уже работает. Если камера находится на передней панели компьютера, вы будете видеть самого себя в видеопотоке реального времени.

Я также реализовал обработчик Tapped для выполнения снимка. Класс MediaCapture содержит методы CapturePhotoToStorageFileAsync и CapturePhotoToStreamAsync. Я выбрал второе решение с сохранением фотографии в потоке в памяти, при котором объект BitmapDecoder может получить биты пикселов. Программа заимствует структуру HSL из программы FingerPaint для повышения насыщенности всех пикселов, после чего создает объект WriteableBitmap для результата:

// ...

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        // ...
        
        bool ignoreTaps = false;

        async protected override void OnTapped(TappedRoutedEventArgs e)
        {
            if (ignoreTaps)
                return;

            // Сохранение фотографии в потоке памяти
            ImageEncodingProperties imageEncodingProps = ImageEncodingProperties.CreateJpeg();
            InMemoryRandomAccessStream memoryStream = new InMemoryRandomAccessStream();
            await mediaCapture.CapturePhotoToStreamAsync(imageEncodingProps, memoryStream);

            // Использование объекта BitmapDecoder для получения массива пикселей
            BitmapDecoder decoder = await BitmapDecoder.CreateAsync(memoryStream);
            PixelDataProvider pixelProvider = await decoder.GetPixelDataAsync();
            byte[] pixels = pixelProvider.DetachPixelData();

            // Изменение насыщенности
            for (int index = 0; index < pixels.Length; index += 4)
            {
                Color color = Color.FromArgb(pixels[index + 3],
                                             pixels[index + 2],
                                             pixels[index + 1],
                                             pixels[index + 0]);
                HSL hsl = new HSL(color);
                hsl = new HSL(hsl.Hue, 1.0, hsl.Lightness);
                color = hsl.Color;

                pixels[index + 0] = color.B;
                pixels[index + 1] = color.G;
                pixels[index + 2] = color.R;
                pixels[index + 3] = color.A;
            }

            // Создание объекта WriteableBitmap и его инициализация
            WriteableBitmap bitmap = new WriteableBitmap((int)decoder.PixelWidth, 
                                                         (int)decoder.PixelHeight);
            Stream pixelStream = bitmap.PixelBuffer.AsStream();
            await pixelStream.WriteAsync(pixels, 0, pixels.Length);
            bitmap.Invalidate();

            // Вывод изображения
            image.Source = bitmap;

            // Установка таймера для изображения
            DispatcherTimer timer = new DispatcherTimer
            {
                Interval = TimeSpan.FromSeconds(2.5)
            };
            timer.Tick += OnTimerTick;
            timer.Start();
            ignoreTaps = true;

            base.OnTapped(e);
        }

        private void OnTimerTick(object sender, object e)
        {
            // Отключить таймер
            DispatcherTimer timer = sender as DispatcherTimer;
            timer.Stop();
            timer.Tick -= OnTimerTick;

            // Уничтожение изображения
            image.Source = null;
            ignoreTaps = false;
        }
    }
}

Чтобы изображение не оставалось навечно, программа устанавливает таймер DispatcherTimer на 2,5 секунды. В этот промежуток времени дальнейшие касания игнорируются, но по его истечению фотография просто удаляется с экрана, и приложение возвращается к видеопотоку в реальном времени.

Конечно, цвета с высокой насыщенностью иногда выглядят пугающе :)

Пройди тесты
Лучший чат для C# программистов