(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Использование технологии Direct2D для создания WinRT компонентов

Источник: habrahabr
AlexKravtsov


Эта статья продолжает серию наших рассказов, в которых мы делимся своим опытом разработки визуальных WinRT контролов в стиле Windows 8 UI. 

В прошлый раз мы приводили базовые шаги, необходимые для создания своего WinRT контрола и SDK для него, а сейчас речь пойдёт о применении технологии Direct2D для создания визуальных эффектов в вашем WinRT компоненте.

В данной статье мы рассмотрим процесс создания кругового индикатора aka гейдж (gauge control), у которого стрелка будет размываться при движении.

Примечание: полный код этого проекта вы можете скачать по следующей ссылке: go.devexpress.com/Habr_WinRTSample.aspx



Что такое Direct2D ?


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

Direct2D API разработан компанией Microsoft для создания приложений под управлением операционной системы Windows и для взаимодействия с существующим кодом, который использует GDI, GDI+, или Direct3D.

Когда бывает необходимо использовать Direct2D в своих приложениях?

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

Более подробно узнать про Direct2D и особенности её применения можно в соответствующем разделе MSDN.

Системные требования


Для разработки приложений в стиле Windows 8 UI (METRO) вам понадобятся Windows 8 и Visual Studio 2012. Более подробно об этом можно прочитать в нашей предыдущей статье.

Создание С++ библиотеки с компонентом, использующим Direct2D


Чтобы использовать Direct2D в вашем приложении, надо написать WinRT компонент на C++.

Для этого выполним следующие шаги:
  1. Запустим Visual Studio 2012 (если вы её вообще когда-либо закрываете :-) )
  2. Создадим новый Visual C++ проект типа Windows Runtime Component



    Данная библиотека будет содержать реализацию объявление и реализацию нескольких интерфейсов, предназначенных для отрисовки с помощью технологии Direct2D.
  3. Затем необходимо в свойствах нашего C++ проекта добавить ссылки на библиотеки Direct2D: d2d1.lib и d3d11.lib.



  4. Далее необходимо написать реализацию отрисовки нашего компонента с использованием Direct2D.

    Во-первых, создаем статический класс DrawingFactory с одним единственным методомCreateRenderer, который будет инициализировать библиотеку DirectX, создавать и возвращать экземпляр класса Renderer:

    public ref class DrawingFactory sealed 	{
    public:
        static IRenderer^ CreateRenderer();
    };
    

    Во-вторых, описываем основной интерфейс IRenderer, который будет содержать следующие методы и свойства:
    - метод Reset, предназначенный для пересоздания поверхности для рисования, в нашем случае этоImageBrush с размерами, которые были переданы. В дальнейшем полученную ImageBrush мы будем присваивать какому-либо элементу в визуальном дереве нашего WinRT компонента;
    - свойство TargetBrush, которое будет возвращать созданную кисть в методе Reset и должен содержать результат отрисовки;
    - свойства EnableMotionBlur, MotionBlurAngle, MotionBlurDeviation, которые служат для включения и настройки эффекта размытия при рендеринге примитивов;
    - методы CreateGeometry, DrawGeometry, FillGeometry, предназначенные для создания и рисования геометрий;
    - Кроме того рассматриваемый интерфейс содержит еще несколько простых и понятных методов и свойств, таких как BeginDraw, EndDraw, Clear, TargetWidth и TargetHeight.

    public interface class IRenderer
    {
       property int TargetWidth { int get(); }
       property int TargetHeight { int get(); }
       property ImageBrush^ TargetBrush { ImageBrush^ get(); }
       property bool EnableMotionBlur { bool get(); void set(bool value); }
       property float MotionBlurAngle { float get(); void set(float value); }
       property float MotionBlurDeviation { float get(); void set(float value); }
            
       void Reset(int width, int height);
       void BeginDraw();
       void EndDraw();
       void Clear(Color color);
       IShapeGeometry^ CreateGeometry();
       void DrawGeometry(IShapeGeometry^ geometry, Color color, float width);
       void FillGeometry(IShapeGeometry^ geometry, Color color);
    };
    


    А теперь расскажем о том, как мы будем делать эффект размытия. С Direct2D данная задача решается очень просто. В методе Reset нужно создать стандартный DirectionalBlur effect, и временный битмап (m_inputSource), в который будем рисовать и использовать его в качестве источника для ранее созданного эффекта:

    void Renderer::Reset(int width, int height)
    {
       D3D_FEATURE_LEVEL featureLevels[] = 
       {
          D3D_FEATURE_LEVEL_11_1,
          D3D_FEATURE_LEVEL_11_0,
          D3D_FEATURE_LEVEL_10_1,
          D3D_FEATURE_LEVEL_10_0,
          D3D_FEATURE_LEVEL_9_3,
          D3D_FEATURE_LEVEL_9_2,
          D3D_FEATURE_LEVEL_9_1
       };
        
       ThrowIfFailed(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, 0, 
          D3D11_CREATE_DEVICE_BGRA_SUPPORT, featureLevels, ARRAYSIZE(featureLevels), 
          D3D11_SDK_VERSION, &m_d3dDevice, NULL, &m_d3dContext));
       ThrowIfFailed(m_d3dDevice.As(&m_dxgiDevice));
       ThrowIfFailed(m_d2dFactory->CreateDevice(m_dxgiDevice.Get(), &m_d2dDevice));	
       ThrowIfFailed(m_d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &m_d2dContext));
    
       m_imageSource = ref new SurfaceImageSource(width, height, false);
        IInspectable* inspectable = (IInspectable*) reinterpret_cast<IInspectable*>(m_imageSource);
       ThrowIfFailed(inspectable->QueryInterface(__uuidof(ISurfaceImageSourceNative), (void**)&m_imageSourceNative));
        ThrowIfFailed(m_imageSourceNative->SetDevice(m_dxgiDevice.Get()));
        
       m_imageBrush = ref new ImageBrush();
        m_imageBrush->ImageSource = m_imageSource;
        
       m_targetWidth = width;
       m_targetHeight = height;
    
       ThrowIfFailed(m_d2dContext->CreateEffect(CLSID_D2D1DirectionalBlur, &m_blurEffect));
        
       m_d2dContext->CreateBitmap(D2D1::SizeU(m_targetWidth, m_targetHeight), nullptr, 0, 
       D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET,D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, 
       D2D1_ALPHA_MODE_PREMULTIPLIED)), &m_inputSource);
    
       m_blurEffect->SetInput(0, m_inputSource.Get());
    }
    


    Затем в методе BeginDraw в качестве поверхности для рисования, используем промежуточный битмап m_inputSource, а при завершении рисования в методе EndDraw устанавливаем поверхностьTargetBrush и, в зависимости от значения свойства EnableMotionBlur, рисуем либо эффект, либо промежуточный битмап.

    void Renderer::BeginDraw()
    {
       if (!m_drawingInProcess && (m_d2dContext.Get() != NULL)) 
       {
          m_d2dContext->BeginDraw();	
          m_d2dContext->SetTarget(m_inputSource.Get());
          m_drawingInProcess = true;
       }
    }
    
    void Renderer::EndDraw()
    {
       if (m_drawingInProcess)
       {
          m_d2dContext->EndDraw();
            
          ComPtr<ID2D1Bitmap1> bitmap;	
          PrepareSurface(&bitmap);
          m_d2dContext->SetTarget(bitmap.Get());
          m_d2dContext->BeginDraw();
          m_d2dContext->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));
                
          if (m_motionBlurEnabled) 
          {
             m_blurEffect->SetValue(D2D1_DIRECTIONALBLUR_PROP_STANDARD_DEVIATION, 
                m_motionBlurDeviation);
             m_blurEffect->SetValue(D2D1_DIRECTIONALBLUR_PROP_ANGLE, m_motionBlurAngle);
             m_d2dContext->DrawImage(m_blurEffect.Get());
          }
          else
             m_d2dContext->DrawImage(m_inputSource.Get());
    
          m_d2dContext->EndDraw();
            
          m_d2dContext->SetTarget(NULL);
          m_imageSourceNative->EndDraw();	
          m_drawingInProcess = false;
       }
    }
    



Создание С# библиотеки, содержащей WinRT компонент



На следующем этапе необходимо добавить в наше решение ещё один проект - на этот раз C#. Этот проект представляет собой библиотеку, содержащую WinRT компонент, который будет оперировать с написанной ранее C++ библиотекой.



Для этого проекта нужно добавить ссылки на созданную ранее C++ сборку:



В этой библиотеке будет содержаться собственно сам WinRT Gauge. Чтобы включить его в нашу сборку, добавим новый Templated Control:



Все визуальные элементы, кроме стрелки, будем отрисовывать стандартными средствами WinRT, а именно, зададим в темплейте:

<Style TargetType="local:Gauge">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:Gauge">
                <Grid>
                    <Grid Margin="0,0,0,0">
                        <Rectangle Fill="#FF252525" RadiusX="30" RadiusY="30"/>
                        <Path Stretch="Uniform" VerticalAlignment="Top" HorizontalAlignment="Center" 
                              Stroke="#FFDAA5F8" Fill="#FFD365F8" StrokeThickness="3" 
                              StrokeStartLineCap="Square" StrokeEndLineCap="Square" Margin="150,0,150,0">
                            <Path.Data>
                                <PathGeometry>
                                    <PathGeometry.Figures>
                                        <PathFigure StartPoint="0,0">
                                            <PathFigure.Segments>
                                               <LineSegment Point="0.1,0"/>
                                               <ArcSegment Point="10.1,0" Size="5,5" 
                                                           RotationAngle="180" IsLargeArc="False" 
                                                           SweepDirection="Counterclockwise"/>
                                                <LineSegment Point="10.2,0"/>
                                                <ArcSegment Point="0,0" Size="5.1,5.1" 
                                                            RotationAngle="180" IsLargeArc="False" 
                                                            SweepDirection="Clockwise"/>
                                            </PathFigure.Segments>
                                        </PathFigure>
                                    </PathGeometry.Figures>
                                </PathGeometry>
                            </Path.Data>
                        </Path>
                        <TextBlock Text="{Binding Value, 
                                   RelativeSource={RelativeSource Mode=TemplatedParent}}" 
                                   FontSize="100" Foreground="#FFD365F8"
                                   VerticalAlignment="Top" HorizontalAlignment="Center"/>
                        <Border x:Name="RendererSurface"/>
                    </Grid>
                </Grid>
            </ControlTemplate>  
        </Setter.Value>
    </Setter>
</Style>


В рассматриваемом визуальном представлении компонента есть пустой Border с именем RendererSurface. Данный элемент получим в методе OnApplyTemplate и сохраним в переменную класса, чтобы затем в его свойство Background присвоить Brush из renderer. 

protected override void OnApplyTemplate() {
    base.OnApplyTemplate();
    rendererSurface = (Border)GetTemplateChild("RendererSurface");
}


В конструкторе нашего класса мы должны c помощью DrawingFactory создать IRenderer, а также подписаться на событие SizeChanged. На этом событии будем вызывать метод IRenderer.Reset, для того чтобы размер кисти, в которую будет производится отрисовка, соответствовал размеру компонента. Также подписываемся на статическое событие CompositionTarget.Rendering - на обработчике этого события мы будем рисовать нашу стрелку.

public Gauge() {
    renderer = DrawingFactory.CreateRenderer();
    DataContext = this;
    this.DefaultStyleKey = typeof(Gauge);
    arrowStrokeColor = ColorHelper.FromArgb(0xFF, 0xD3, 0xAA, 0xF8);
    arrowFillColor = ColorHelper.FromArgb(0xFF, 0xD3, 0x65, 0xF8);
    this.SizeChanged += Gauge_SizeChanged;
    CompositionTarget.Rendering += CompositionTarget_Rendering;
}

void Gauge_SizeChanged(object sender, SizeChangedEventArgs e) {
    renderer.Reset((int)e.NewSize.Width, (int)e.NewSize.Height);
    rendererSurface.Background = renderer.TargetBrush;
}

void CompositionTarget_Rendering(object sender, object e) {
    Render();
}


Теперь нам остается только добавить одно свойство зависимости Value и отрисовать геометрию стрелки:

static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", 
    typeof(double), typeof(Gauge), new PropertyMetadata(0.0, ValuePropertyChanged));
        
public double Value {
    get { return (double)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

public static void ValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    Gauge gauge = d as Gauge;
    if (gauge != null)
        gauge.renderer.EnableMotionBlur = true;
}

void Render() {
    double angle = Math.PI * Value / 1000 - Math.PI / 2.0;
    IShapeGeometry geometry = CreateArrowGeometry(angle);
    renderer.BeginDraw();
    renderer.MotionBlurAngle = (float)(angle / Math.PI * 180);
    renderer.MotionBlurDeviation = 5.0f;
    renderer.Clear(Colors.Transparent);
    renderer.FillGeometry(arrow, ColorHelper.FromArgb(0xFF, 0xD3, 0x65, 0xF8));
    renderer.DrawGeometry(arrow, ColorHelper.FromArgb(0xFF, 0xD3, 0xAA, 0xF8), 3.0f);
    renderer.EndDraw();
    if (renderer.EnableMotionBlur)
        renderer.EnableMotionBlur = false;
}


Вот и всё. Наш компонент готов, и теперь мы можем использовать его в реальном приложении.

Использование WinRT компонента Gauge


Для того, чтобы продемонстрировать работу нашего компонента, добавим ещё один проект в наше решение. Пусть это будет шаблон Blank App (XAML). Назовём наш тестовый проект GaugeMatrix:



Затем в этот проект добавим ссылки на проекты Gauge и DrawingLayer, созданные ранее:



Далее добавим Grid в MainPage.xaml и создадим в нём две строки и два столбца.

<Grid.ColumnDefinitions>
    <ColumnDefinition Width="*"/>
    <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
    <RowDefinition Height="*"/>
    <RowDefinition Height="*"/>
</Grid.RowDefinitions>


После этого в каждую ячейку кладем наш компонент Gauge, а также Slider длядинамического изменения значения, отображаемого с помощью стрелки на Gauge контроле:

<Grid Grid.Column="0" Grid.Row="0" Margin="30">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Slider Grid.Row="0" Name="ValueSlider1" Minimum="0" Maximum="1000" Value="0" 
        Width="1000" Margin="50,0,50,0"/>
    <g:Gauge Grid.Row="1" Value="{Binding ElementName=ValueSlider1, Path=Value}"/>
</Grid>


Делаем проект GaugeMatrix стартовым, запускаем приложение и… всё, у вас получилось использовать рисование с помощью Direct2D в вашем WinRT приложении! Поздравляем!



Примечание: полный код этого проекта вы можете скачать по следующей ссылке: go.devexpress.com/Habr_WinRTSample.aspx

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 20.09.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год.
Microsoft Office 365 Бизнес. Подписка на 1 рабочее место на 1 год
Microsoft 365 Business Basic (corporate)
Microsoft 365 Apps for business (corporate)
Microsoft Windows Professional 10, Электронный ключ
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
CASE-технологии
Программирование на Microsoft Access
Corel DRAW - от идеи до реализации
Новости мира 3D-ускорителей
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100