Статьи

Создание программы просмотра изображений и видео для Microsoft Surface 2.0 в кратчайшие сроки

Мне посчастливилось получить доступ к совершенно новой Surface 2.0 (Samsung SUR40) в последнее время, и я хотел попробовать свои силы в разработке платформы. Как и в большинстве случаев, самый простой способ научиться чему-либо — это создать небольшой проект — для начала я собираюсь создать очень простой образ «Аттрактор». Это позволит вам перемещать, изменять размеры и вращать изображения и видео на экране устройства. Сообщество разработчиков Surface кажется маленьким плащом и кинжалом с очень небольшим количеством информации; что-то, надеюсь, я могу внести позитивный вклад в изменение, документируя свое путешествие.

Фон на поверхности 2.0

Microsoft объединилась с Samsung, чтобы создать вторую версию Surface, и, как обычно, Samsung действительно подошла к пластине, чтобы создать удивительное оборудование.

Оригинальная поверхность содержала инфракрасные камеры, направленные вверх на экран, чтобы увидеть, что происходит. Это означало, что у вас осталось устройство, которое было буквально таким же глубоким, как целый журнальный столик. Новое устройство имеет глубину всего 4 дюйма и использует новый тип датчиков, называемый Pixel Sense, который устраняет необходимость в глубине — снова показывая, как много знает Samsung о создании смехотворно тонких экранов.

image4

Спецификации устройства:

  • Двухъядерный процессор AMD Athlon ™ II X2 2,9 ГГц
  • 40-дюймовый сенсорный ЖК-дисплей
  • Соотношение сторон 16: 9
  • Разрешение 1920×1080
  • 50 точек мультитач

Что я хочу построить

То, что я собираюсь построить, — это приложение, которое отображает мои изображения и видео для отображения в зоне приема.

Это довольно распространенное использование для поверхности Microsoft — прием порно для ваших посетителей.

Я собираюсь назвать это приложение OpenAttractor, так как оно будет приложением с открытым исходным кодом, чтобы привлечь внимание моих посетителей и позволить им просмотреть некоторые из наших работ.

Это приложение позволит пользователям просматривать, перемещать и изменять размеры моих изображений и видео.

Вещи, которые вам понадобятся

  • Загрузите и установите Surface 2.0 SDK здесь .
  • Машина с Windows 7 (Surface 2.0 работает на Windows 7).
  • Visual Studio 2010 Express или выше.

ВНИМАНИЕ, НЕ ДЕЛАЙТЕ ЭТОГО НА ДОМУ

Как я уже упоминал в своем посте Kinect SDK , я по профессии веб-разработчик. У меня нет большого опыта работы с WPF / Windows Forms. Поэтому любой код, который вы видите в этом посте и который, по вашему мнению, смешно «неправильный способ делать что-то», вы, вероятно, будете вполне в состоянии сделать такой комментарий — я хотел бы услышать от вас, как вы поступили бы по-другому или где я ошибся в комментариях ниже. Это мое путешествие в мир Surface SDK, будьте осторожны 🙂

Создайте новое приложение Surface в Visual Studio.

image5

Первое, что мне нужно, это фоновое изображение для моего приложения.

Я нашел хорошее изображение Вуд Грэйна 1920×1080, которое хорошо подойдет для этой цели.

WoodBackground_thumb

Поэтому поместите это в папку « Ресурсы » вашего недавно созданного проекта и включите в свой проект.

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

image13

Удивительная красота поверхности SDK

Ранее я писал о том, насколько элегантен Kinect SDK , и Surface SDK ничем не отличается.

За несколько строк кода вы можете создавать удивительно мощные мультитач-приложения.

Откройте файл SurfaceWindow1.xaml и вставьте следующее на страницу:

<s:SurfaceWindow x:Class="OpenAttractor.SurfaceWindow1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="http://schemas.microsoft.com/surface/2008" xmlns:Properties="clr-namespace:OpenAttractor.Properties"
                 xmlns:OpenAttractor="clr-namespace:OpenAttractor" Title="OpenAttractor">
    <s:SurfaceWindow.Background>
        <ImageBrush ImageSource="/OpenAttractor;component/Resources/WoodBackground.png"/>
    </s:SurfaceWindow.Background>
        <s:ScatterView x:Name="ScatterContainer">
            <s:ScatterViewItem>
                <Image Source="/OpenAttractor;component/Pictures/Desert.jpg"/>
            </s:ScatterViewItem>
            <s:ScatterViewItem>
                <Image Source="/OpenAttractor;component/Pictures/Hydrangeas.jpg"/>
            </s:ScatterViewItem>
            <s:ScatterViewItem>
                <Image Source="/OpenAttractor;component/Pictures/Jellyfish.jpg"/>
            </s:ScatterViewItem>
            <s:ScatterViewItem>
                <Image Source="/OpenAttractor;component/Pictures/Koala.jpg"/>
            </s:ScatterViewItem>
            <s:ScatterViewItem>
                <Image Source="/OpenAttractor;component/Pictures/Lighthouse.jpg"/>
            </s:ScatterViewItem>
            <s:ScatterViewItem>
                <Image Source="/OpenAttractor;component/Pictures/Penguins.jpg"/>
            </s:ScatterViewItem>
            <s:ScatterViewItem>
                <Image Source="/OpenAttractor;component/Pictures/Tulips.jpg"/>
            </s:ScatterViewItem>
        </s:ScatterView>
</s:SurfaceWindow>

 

При этом используется один из элементов управления Surface SDK, элемент управления ScatterView , который позволяет изменять размеры объектов и содержать базовые физические свойства , позволяя перемещать их по экрану.

Если мы запустим это приложение, мы уже на полпути!

image17

Слово о тестировании

Поверхность — это устройство Multi-Touch , рассчитанное на одновременную работу до 50 точек.

Я разрабатываю свое приложение на своем ноутбуке. У меня нет поддержки мультитач. Это означает, что я не могу проверить свой «шаг / растяжение для увеличения» без использования чего-то особенного.

К счастью для меня, Surface 2.0 SDK включает в себя инструмент под названием Microsoft Surface Input Simulator, который поддерживает одновременное размещение нескольких пальцев на экране.

Чтобы проверить это, откройте Симулятор ввода. Тогда запустите наше приложение.

Выберите инструмент «Палец» и щелкните левой кнопкой мыши на одном углу изображения. Удерживая левую кнопку мыши нажатой, нажмите правую кнопку мыши. Это поместит и удержит виртуальный палец на экране.

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

image21

Удивительно простой и потрясающий; Мы еще даже не написали код!

Теперь для видео.

Добавление видеоплеера

Теперь, когда я отсортировал изображение по моим точечным представлениям, мне также понадобилась возможность воспроизводить видео. Мне нужно добавить простой видеоплеер, чтобы люди, играющие с приложением, могли просматривать примеры видео.

Итак, давайте разберемся с этим. Я хочу видео плеер, который будет включать немного модульной функциональности. Чтобы избежать проблем с хранением большого количества переменных в одном классе, я собираюсь создать пользовательский элемент управления WPF, чтобы я мог инкапсулировать все функциональные возможности в один объект.

image4 [1]

Затем я создал несколько изображений для использования в своем контроле и добавил их в свою папку «Ресурсы».

resources_play_up3resources_pause_btn2resources_play_btn2resources_rew_btn2

После этого я добавил следующий код в файл XAML моего пользовательского элемента управления WPF, чтобы получить все свои элементы управления видео:

<UserControl x:Class="OpenAttractor.VideoPlayer"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:s="clr-namespace:Microsoft.Surface.Presentation.Controls;assembly=Microsoft.Surface.Presentation"
             DataContext="{Binding RelativeSource={RelativeSource Self}}"
             mc:Ignorable="d">
    <Grid>
         <Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Panel.ZIndex="10">
            <MediaElement x:Name="videoPlayer" ScrubbingEnabled="True" Source="{Binding Path=Source}" Loaded="videoPlayer_Loaded" LoadedBehavior="Manual" UnloadedBehavior="Stop" Stretch="UniformToFill" />
            <Image Source="/OpenAttractor;component/Resources/video_overlay.png" x:Name="Overlay">
                …
            </Image>
            <s:SurfaceButton Background="Transparent" x:Name="PlayButton" Click="PlayButton_Click" HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
                <s:SurfaceButton.Content>
                    <Image Source="/OpenAttractor;component/Resources/video_playbutton_large.png" />
                </s:SurfaceButton.Content>
                …
            </s:SurfaceButton>
        </Grid>
        <Grid Height="40" Name="PlayerControls" VerticalAlignment="Bottom" Margin="5,0,5,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition ColumnDefinition.Width="40" />
                <ColumnDefinition ColumnDefinition.Width="40" />
                <ColumnDefinition ColumnDefinition.Width="*" />
            </Grid.ColumnDefinitions>
            <s:SurfaceButton x:Name="RewindButton" Click="RewindButton_Click"  VerticalAlignment="Top">
                <s:SurfaceButton.Background>
                    <ImageBrush ImageSource="/OpenAttractor;component/Resources/video_rewindbutton.png" />
                </s:SurfaceButton.Background>                
            </s:SurfaceButton>
            <s:SurfaceButton x:Name="PauseButton" Click="PauseButton_Click"  Grid.Column="1">
                <s:SurfaceButton.Background>
                    <ImageBrush ImageSource="/OpenAttractor;component/Resources/video_pausebutton.png" />
                </s:SurfaceButton.Background>
                …
                </s:SurfaceButton.Style>
            </s:SurfaceButton>
            <s:SurfaceButton x:Name="PlayButtonSmall" Grid.Column="1" Click="PlayButtonSmall_Click">
                <s:SurfaceButton.Background>
                    <ImageBrush ImageSource="/OpenAttractor;component/Resources/video_playbutton_small.png" />
                </s:SurfaceButton.Background>
                …
            </s:SurfaceButton>
            <Border Border.Background="#881E1E1E" FrameworkElement.Margin="10,15,0,15" FrameworkElement.Height="4" Grid.Column="2" />
            <Border Border.Background="#7FC9FA" FrameworkElement.Margin="10,15,0,15" FrameworkElement.Height="4" Grid.Column="2">
                <UIElement.RenderTransform>
                    <ScaleTransform ScaleTransform.ScaleX="{Binding Path=CurrentVideoProgress}" />
                </UIElement.RenderTransform>
            </Border>
            …
        </Grid>
    </Grid>
</UserControl>

 Затем я добавил следующий код в код файла XAML моего элемента управления:

 

namespace OpenAttractor
{
    public partial class VideoPlayer : UserControl
    {
        public string Source { get { return (string)GetValue(_sourceProperty); } set { SetValue(_sourceProperty, value); } }
        public static readonly DependencyProperty _sourceProperty = DependencyProperty.Register("Source", typeof(string), typeof(VideoPlayer), new FrameworkPropertyMetadata(String.Empty));

        public double CurrentVideoProgress { get { return (double)GetValue(_currentVideoProgress); } set { SetValue(_currentVideoProgress, value); } }
        public static readonly DependencyProperty _currentVideoProgress = DependencyProperty.Register("CurrentVideoProgress", typeof(double), typeof(VideoPlayer), new FrameworkPropertyMetadata((double)0));

        public bool VideoIsPlaying { get { return (bool)GetValue(_videoIsPlaying); } set { SetValue(_videoIsPlaying, value); } }
        public static readonly DependencyProperty _videoIsPlaying = DependencyProperty.Register("VideoIsPlaying", typeof(bool), typeof(VideoPlayer), new FrameworkPropertyMetadata(false));

        private Timer _playTimer;

        public VideoPlayer()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(VideoPlayerLoaded);
        }

        void VideoPlayerLoaded(object sender, RoutedEventArgs e)
        {
            videoPlayer.MediaEnded += delegate(object o, RoutedEventArgs args)
            {
                videoPlayer.Position = new TimeSpan(0, 0, 0, 0);
                videoPlayer.Play();
            };

            _playTimer = new Timer {Interval = 300};
            _playTimer.Elapsed += delegate(object o, ElapsedEventArgs args)
                                      {
                                          Application.Current.Dispatcher.BeginInvoke(
                                              DispatcherPriority.Background,
                                              new Action(() => CurrentVideoProgress =
                                                               videoPlayer.Position.TotalMilliseconds/
                                                               videoPlayer.NaturalDuration.TimeSpan.TotalMilliseconds));
                                      };
        }

        private void PlayButton_Click(object sender, RoutedEventArgs e)
        {
            videoPlayer.Play();
            _playTimer.Start();
            VideoIsPlaying = true;
        }

        private void RewindButton_Click(object sender, RoutedEventArgs e)
        {
            videoPlayer.Position = new TimeSpan(0, 0, 0, 0);
        }

        private void PauseButton_Click(object sender, RoutedEventArgs e)
        {
            videoPlayer.Pause();
            _playTimer.Stop();
            VideoIsPlaying = false;
        }

        private void videoPlayer_Loaded(object sender, RoutedEventArgs e)
        {
            ((MediaElement)sender).Play();
            ((MediaElement)sender).Position = new TimeSpan(0, 0, 0, 1);
            ((MediaElement)sender).Pause();
        }

        private void PlayButtonSmall_Click(object sender, RoutedEventArgs e)
        {
            videoPlayer.Play();
            _playTimer.Start();
            VideoIsPlaying = true;
        }
    }
}

Вы заметите немного странный подход в том, что я играю, а затем приостановите видео, как только оно будет загружено. Это связано с тем, что элемент управления MediaElement не загружает первый видеокадр до его первого воспроизведения. Поскольку я хочу, чтобы первый кадр показывался для моего видео в качестве тизера, я воспроизводлю и приостанавливаю видео прямо вверх, чтобы убедиться, что вы можете видеть, как выглядит первый кадр видео.

private void videoPlayer_Loaded(object sender, RoutedEventArgs e)
{
    ((MediaElement)sender).Play();
    ((MediaElement)sender).Position = new TimeSpan(0, 0, 0, 1);
    ((MediaElement)sender).Pause();
}

 

Я также использую фоновый элемент управления, чтобы показать мой прогресс для видео. При поиске цвета для моего индикатора выполнения я открыл видео в Windows Media Player и моем любимом инструменте выбора цвета ColorPic и «схватил» цвет прямо с индикатора выполнения проигрывателя.

образ

После этого мне нужно добавить пользовательский элемент управления WPF на страницу моего основного приложения Surface, поэтому я добавил следующее в XAML моей главной страницы:

<s:ScatterViewItem>
    <OpenAttractor:VideoPlayer Source="Videos/Wildlife.wmv" />
</s:ScatterViewItem>

 

После всего этого у меня есть очень удобное приложение с очень небольшим кодом:

image12

Делая все это динамичным

До сих пор я использовал созданные вручную объекты XAML для каждого из моих изображений и видео. Это далеко не гибкий. Что я хочу сделать, так это настроить параметр, содержащий путь к видео и изображениям, и при загрузке приложения он будет динамически создавать мои элементы управления ScatterView.

Поэтому я удалил весь контент главной страницы XAML и заменил его удаленными вручную элементами scatterview.

<s:SurfaceWindow x:Class="OpenAttractor.SurfaceWindow1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="http://schemas.microsoft.com/surface/2008"
    Title="OpenAttractor">
    <s:SurfaceWindow.Background>
        <ImageBrush ImageSource="/OpenAttractor;component/Resources/WoodBackground.png"/>
    </s:SurfaceWindow.Background>
    <s:ScatterView x:Name="ScatterContainer"/>
</s:SurfaceWindow>

А затем добавил параметр app.config для моего пути:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="VideoAssetsPath" value="C:\Users\Public\Videos\Sample Videos"/>
    <add key="PhotoAssetsPath" value="C:\Users\Public\Pictures\Sample Pictures"/>
  </appSettings>
</configuration>

А затем добавьте следующий код в код XAML главной страницы:

public SurfaceWindow1()
{
    InitializeComponent();

    // Add handlers for window availability events
    AddWindowAvailabilityHandlers();
    Loaded += new RoutedEventHandler(SurfaceWindow1_Loaded);
}

void SurfaceWindow1_Loaded(object sender, RoutedEventArgs e)
{
    var images = Directory.GetFiles(ConfigurationManager.AppSettings["PhotoAssetsPath"], "*.jpg");
    var videos = Directory.GetFiles(ConfigurationManager.AppSettings["VideoAssetsPath"], "*.wmv");

    foreach (var imagePath in images)
    {
        var imageControl = new Image();
        var myBitmapImage = new BitmapImage();
        myBitmapImage.BeginInit();
        myBitmapImage.UriSource = new Uri(imagePath);
        myBitmapImage.EndInit();
        imageControl.Source = myBitmapImage;
        var scatterView = new ScatterViewItem { Content = imageControl };
        ScatterContainer.Items.Add(scatterView);
    }

    foreach (var videoPath in videos)
    {
        var videoControl = new VideoPlayer { Source = videoPath };
        var scatterView = new ScatterViewItem { Content = videoControl };
        ScatterContainer.Items.Add(scatterView);
    }
}

Все сделано! Теперь у меня есть гибкий способ представлять свои фотографии и видео для просмотра на Surface 2.0 — и все это за очень короткое время и с очень небольшим кодом .

Это показывает, насколько удивительна и элегантна работа, созданная командой Surface SDK, чтобы вы могли приступить к работе на платформе. Они отвлекли всю тяжелую работу, чтобы сделать приложения для создания поверхностей легкими, как 1-2-3.

Резюме

В то время как ценник Surface 2.0 может быть недостижим для многих компаний, Microsoft, как обычно, сделала все возможное, когда дело доходит до развития истории. Они взяли на себя то, что кажется частью разработки, которую такой человек, как я, непритязательный веб-разработчик, обычно считал бы важной задачей: создание мультисенсорного приложения; и сделал его потрясающим опытом разработки с простым в освоении SDK.

В последнее время все, к чему я прикасался, от Microsoft (My Windows Phone, My Xbox Kinect, My Netduino plus, ASP.Net MVC) получил SDK, который был не чем иным, как мечтой разработчика. Когда вы первый попасть в развитие, всегда есть момент , когда вы , когда остановиться и понять , что вы можете взять на мир с вещами , которые вы можете построить — это, как правило , в тот момент , вы подсесть как разработчик. По мере развития вашей карьеры иногда бывает трудно воспроизвести это чувство, поскольку вы привыкнете к тому, что делаете в своей повседневной работе.

Microsoft Surface SDK для Samsung Surface 2.0 возвращает это чувство в пики.

Если у вас есть доступ к одному из этих удивительных устройств, скачайте SDK и играйте!

Что касается моего приложения OpenAttractor , как я уже упоминал; во время моих путешествий, изучающих разработку Surface 2.0, я заметил, что многие разработчики на платформе Surface, кажется, держат свои вещи, когда дело доходит до совместного использования кода.

Поэтому я разместил свое приложение на GitHub ЗДЕСЬ и продолжу его разрабатывать в течение долгого времени.