Использование графических эффектов в приложениях UWP с помощью Win2D


    Знакомьтесь: Win2D это легкое в использование Windows Runtime API для более удобного использования возможностей DirectX. Прорисовка графики осуществляется с ускорением GPU. Win2D доступно для разработчиков C#, C++ и VB и в Windows 8.1 и в Windows 10.

    С помощью Win2D вы сможете рисовать фигуры, линии, текст и изображения, а также добавлять ко всему этому различные эффекты. Кроме того, можно добавить какие-то эффекты к видеоизображению.

    Предлагаю рассмотреть на примерах основной функционал библиотеки.

    Установить Win2D.uwp можно из Visual Studio с помощью диспетчера пакетов NuGet. Github-страничка этого open-source проекта находится здесь. Англоязычная документация здесь: Win2D documentation

    После установки в заголовок XAML страницы можно добавить:

    xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"

    А в разметку страницы элемент управления CanvasControl:

    <canvas:CanvasControl Width="500" Height="300" x:Name="canv" Draw="CanvasControl_Draw" ClearColor="LightGray"/>

    Это не тот же самый элемент Canvas, который является элементом компоновки страницы и может в качестве хоста содержать в себе другие элементы. Какое-то сходство между Canvas и CanvasControl есть, но производительность совершенно разная.

    При прорисовке элемента CanvasControl происходит событие Draw, которому в примере назначен обработчик события CanvasControl_Draw. Официальный пример предлагает нарисовать для начала фигуру (эллипс) и текст:

    void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args)
       {
           args.DrawingSession.DrawEllipse(155, 115, 80, 30, Colors.Black, 3);
           args.DrawingSession.DrawText("Hello, world!", 100, 100, Colors.Yellow);
       }
    

    Вот что получится:



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

    void Page_Unloaded(object sender, RoutedEventArgs e)
      {
          this.canv.RemoveFromVisualTree();
          this.canv = null;
      }

    Пока что ничего необычного. То же самое можно нарисовать и используя фигуры XAML. Кстати, примеры рисования в обычном XAML вы можете найти здесь: Draw shapes

    Поработаем с изображениями. Это гораздо интереснее. Я добавил в проект файл mydog.jpg и выбрал в свойствах действие при сборке «Содержание» (если быть точным, то даже не выбирал, — оно выбралось само по умолчанию).

    За изображения в Win2D отвечает класс CanvasBitmap из пространства имен Microsoft.Graphics.Canvas. Добавим в наше приложение переменную с именем cbi:

    CanvasBitmap cbi;

    Загрузка картинки происходит во время события элемента Canvas названием CreateResources, поэтому в наш XAML код добавим это событие:

    <canvas:CanvasControl Width="500" Height="300" x:Name="canv"
                Draw="CanvasControl_Draw" CreateResources="CanvasControl_CreateResources" ClearColor="LightGray"/>

    Сама загрузка происходит с помощью следующего кода:

    cbi = await CanvasBitmap.LoadAsync(sender, "mydog.jpg");

    Так как она происходит асинхронно, то нам предлагается вынести ее в отдельный таск:

    private void CanvasControl_CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
           {
               args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
           }
    
           async Task CreateResourcesAsync(CanvasControl sender)
           {
               cbi = await CanvasBitmap.LoadAsync(sender, "mydog.jpg");
           }

    TrackAsyncAction в данном случае требует чтобы таск CreateResourcesAsync завершился до окончания CreateResources. Остается загрузить в Canvas:

    private void CanvasControl_Draw(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
         {
             args.DrawingSession.DrawImage(cbi);
         }



    Загрузилось, но не особо пока что интересно просто отображать картинку. Добавим всего пару строк кода и получим эффект размытия по Гауссу

    var blur = new GaussianBlurEffect();
      blur.BlurAmount = 1.7f;
      blur.Source = cbi;
      args.DrawingSession.DrawImage(blur);
    



    Полный списко эффектов доступен на странице описания пространства имен Microsoft.Graphics.Canvas.Effects документации. Кстати, эффекты также можно создавать еще и с помощью Lumia Imaging SDK. Исходный код Lumia-imaging-sdk можно тоже найти на GitHub. Кроме того, какие-то эффекты доступны с помощью пространства имен Windows.UI.Composition.

    Иногда возникает необходимость отобразить графику не сразу, а во время исполнения программы. Сохранить ее в файл, вывести на экран или получить массив пикселей. Такой вариант работы называется Offscreen drawing.

    Скажем, вот пример, в котором совсем не используется XAML элемент CanvasControl, а используется простой элемент image

    <Image x:Name="img" Width="500" Height="300"></Image>

    Обработать картинку и отобразить ее, добавив эффект насыщения, можно так:

    
      CanvasDevice device = CanvasDevice.GetSharedDevice();
      CanvasRenderTarget offscreen = new CanvasRenderTarget(device, 500, 300, 96);
    
      cbi = await CanvasBitmap.LoadAsync(device, "mydog.jpg");
    
      using (var ds = offscreen.CreateDrawingSession())
      {
          var satur = new SaturationEffect();
          satur.Saturation = 0.2f;
          satur.Source = cbi;
          ds.DrawImage(satur);
      }
    
      using (var stream = new InMemoryRandomAccessStream())
      {
          stream.Seek(0);
          await offscreen.SaveAsync(stream, CanvasBitmapFileFormat.Png);
    
          BitmapImage image = new BitmapImage();
          image.SetSource(stream);
          img.Source = image;
      }

    Мне так даже больше нравится (хотя о сравнении производительности сказать ничего не могу)



    Для того чтобы составить небольшую композицию и наложить текст на картинку нам понадобятся пространство имен:

    using Microsoft.Graphics.Canvas.Text;

    И подобный сниппет:

    CanvasDevice device = CanvasDevice.GetSharedDevice();
      CanvasRenderTarget offscreen = new CanvasRenderTarget(device, 500, 300, 96);
    
      cbi = await CanvasBitmap.LoadAsync(device, "mydog.jpg");
    
      using (var ds = offscreen.CreateDrawingSession())
      {
          ds.DrawImage(cbi);
          var format = new CanvasTextFormat()
          {
              FontSize = 32,
              HorizontalAlignment = CanvasHorizontalAlignment.Left,
              VerticalAlignment = CanvasVerticalAlignment.Top,
              WordWrapping = CanvasWordWrapping.Wrap,
              FontFamily = "Decor",
          };
    
          using (CanvasTextLayout tl = new CanvasTextLayout(ds, "10 июня 2010", format, 250, 50)) // ширина 250 и высота 50
          {
              ds.DrawTextLayout(tl, 10, 10, Color.FromArgb(120, 20, 20, 20));
          }
    
          format.Dispose();
      }
    
      using (var stream = new InMemoryRandomAccessStream())
      {
          stream.Seek(0);
          await offscreen.SaveAsync(stream, CanvasBitmapFileFormat.Png);
    
          BitmapImage image = new BitmapImage();
          image.SetSource(stream);
          img.Source = image;
      }
    



    Объединить 2 изображения можно с помощью эффекта blend:

    CanvasBitmap cbi;
      CanvasBitmap cbi2;
    
      CanvasDevice device = CanvasDevice.GetSharedDevice();
      CanvasRenderTarget offscreen = new CanvasRenderTarget(device, 500, 300, 96);
    
      cbi = await CanvasBitmap.LoadAsync(device, "mydog.jpg");
      cbi2 = await CanvasBitmap.LoadAsync(device, "present.png");
    
      using (var ds = offscreen.CreateDrawingSession())
      {
          BlendEffect blendEffect = new BlendEffect()
           {
              Background = cbi,
              Foreground = cbi2,
              Mode = BlendEffectMode.SoftLight
           };
    
          ds.DrawImage(blendEffect);
      }
    
      using (var stream = new InMemoryRandomAccessStream())
      {
          stream.Seek(0);
          await offscreen.SaveAsync(stream, CanvasBitmapFileFormat.Png);
    
          BitmapImage image = new BitmapImage();
          image.SetSource(stream);
          img.Source = image;
      }
    



    Некоторые эффекты можно комбинировать. С помощью следующего сниппета, комбинирующего множество эффектов можно получить эффект свечения текста

    
      var myTextBitmap = new CanvasRenderTarget(sender, 300, 100);
      using (var ds = myTextBitmap.CreateDrawingSession())
      {
          ds.Clear(Color.FromArgb(0, 0, 0, 0));
          ds.DrawText("Неоновый текст!", 0, 0, Colors.White, new CanvasTextFormat
          {
              FontSize = 24,
              FontWeight = Windows.UI.Text.FontWeights.Bold
          });
      }
    
      var effectGraph = new CompositeEffect();
      effectGraph.Mode = CanvasComposite.Add;
    
      effectGraph.Sources.Add(new ColorMatrixEffect
      {
          Source = new GaussianBlurEffect
          {
              Source = new MorphologyEffect
              {
                  Source = myTextBitmap,
                  Mode = MorphologyEffectMode.Dilate,
                  Width = 7,
                  Height = 4
              },
              BlurAmount = 3f
          },
          ColorMatrix = new Matrix5x4
          {
              M11 = 0f, M12 = 0f, M13 = 0f, M14 = 0f,
              M21 = 0f, M22 = 0f, M23 = 0f, M24 = 0f,
              M31 = 0f, M32 = 0f, M33 = 0f, M34 = 0f,
              M41 = 0f, M42 = 1f, M43 = 0f, M44 = 1f,
              M51 = 1f, M52 = -0.5f, M53 = 0f, M54 = 0f
          }
      });
    
      effectGraph.Sources.Add(myTextBitmap);
      args.DrawingSession.DrawImage(effectGraph,100,100);
    



    Интересный текстовый эффект, который очень часто ассоциируется с Win2D это анимационный эффект горящего текста.



    Прямые ссылки на BurningTextExample.xaml и BurningTextExample.xaml.cs примера я, пожалуй, даже оставлю здесь: BurningTextExample.xaml / BurningTextExample.xaml.cs.

    Точно также как и в случае с UWP Community Toolkit, есть возможность скачать приложение с примерами использования и для Win2D. Приложение называется Win2D Example Gallery. Исходный код приложения находится на github.

    Лично мне больше всего понравилась следующая картинка:



    Как оказалось, она полностью нарисована с помощью DrawLine, DrawEllipse, DrawCircle и т.п.

    Библиотека обновляется регулярно и поэтому можно ожидать новые красивые эффекты.
    Поделиться публикацией

    Комментарии 15

      0
      Всё это очень интересно, но как этим пользоваться в условиях MVVM?
        0
        Win2D это:
        — отскок в сторону от MVVM, нет привычного Binding, вся графика code behind
        — развитие GDI+ поверх Direct3D (для любителей WinForms очень простой переход)
        — ускорение графики раз в 100 в сравнении с MVVM векторной графикой.

        Поэтому, если элементов у вас немного, тогда MVVM. Если элементов много или много анимации — Win2D.
        Можно мешать MVVM и Win2D, т.к. CanvasXXX (а их сейчас уже четыре) является обычной прямоугольной областью, которая встраивается в визуальное дерево XAML
          0
          это скорее развитие Direct2D, чем GDI. У них схожее API в чем-то, но первый гораздо быстрее.

          Так то его можно использовать и начиная с Windows 7(Vista если есть Platform Update), но всякие новомодные эффекты ввели только с 8/8.1
          Есть библиотечки типа SharpDX, которые дают доступ к Direct2D даже для WinForms и WPF, отлично работают.

            +1
            На мой взгляд, это не отскок, это логика на стороне view. Никто ведь не мешает создавать и использовать зависимые свойства при работе с win2d.
          0
          А библиотеку для графиков не находили? Чтобы 10к разных можно было рисовать.
            0
            Графики 2D функции, y=f(x)?

            1. Нарисовать сетку
            2. Нарисовать 10к Polyline полупрозрачных, чтобы лучше были видны сгустки.

            По минимуму можно в десяток строк кода уложиться.
              0
              Нет, ну нужно же чтобы нормально работало на разных диагоналях экрана и ориентации.
              Плюс lifetime подхватывать. Не так уж и просто.
            +1
            Последняя картинка напомнила, как я экспериментировал с аппроксимацией изображений полигонами. Дальше треугольников не продвинулся, к сожалению.
            image
              0
              а как рисовать анимацию? например нарисовать прямоугольник при движении мыши?
              или сделать прямоугольник что-бы пульсировал и при этом мышью рисовать что-то вокруг?
                0
                Есть ли где-нибудь пример, как с помощью этого нарисовать тень от элемента?
                  0
                  More TextEffects in Win2D — вот по этой сылке можно найти пример использования класса ShadowEffect Смотрите пример Text with shadow
                  0
                  Было бы интересно сравнить со https://skia.org/ и аналогами.
                    0
                    Для сравнения нужно тесты писать.
                    Можно попробовать отобразить фрагменты OpenStreetMap.
                      0
                      Поддерживаю, тем более учитывая наличия Skia Sharp, необходимость в Win2D сомнительна.
                        0

                        Win2d тормоз жуткий. Максимум что вы с него сможете выжать это 10`000 линий/прямоугольников/вообще объектов.
                        Вывод текста (метод drawText) — это вообще пиши пропало.
                        Единственное но — у него есть возможность рисовать spriteBatch (вывод картинки) — только в этом случае производительность будет реально упираться в GPU (для примера у меня более миллиона 1000х1000 картинок может вывести).


                        Для примера qt — 200`000 линий рисует при 60 фпс.
                        При первой же возможности переходите на SkiaSharp (если нужен C#).


                        P.S. Более того skia с ganesh surface'ом (opengl/directx аппаратное ускорение) умеет использовать NV_path_rendering. Практически в 100 раз быстрее остальных способов (на сайте нвидии демки есть, можете попробовать).

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

                      Самое читаемое