Rescuer-la: Невероятный C# или avalonia UI в действии
Вот уже пол года я в свободное время занимаюсь некоммерческим open source проектом rescuer-la для не коммерческой организации Лиза Алерт. Суть этого проекта – использовать нейроные сети и ML для распознавания потерявшихся в лесу людей по фотографиям получаемым с дронов. О самом проекте в общем я уже рассказывал на конференции Data Fest 6. Вероятно я ещё напишу статью о нем позже.
Как уже говорилось проект open source и по этому в рамках этого проекта были применены несколько необычных и смелых подходов. Об одном из них сегодня и пойдёт речь, а именно о там как было создано кросс платформенное desktop приложение.
Почему C#?
Передо мной встала задача разработать полностью платформонезависимое приложение для настольных пк, притом сделать это быстро. В свое время я довольно плотно работал с C# и в частности с wpf. По этому мне хотелось получить нечто такое же. Я знал что существует Net Core (я уже писал о нем в предыдущих статьях). Но Net core подходит для разработки Web сервисов. Как быть? Я стал искать варианты и нашёл один – avalonia ui! Посмотрев на фреймворк я увидел что он находится в beta версии 0.7. Интересно – подумал я и выбор в пользу C# стал для меня очевиден.
AvaloniaUI
Для полного понимания этой концепции стоит посмотреть выступление Никиты Цуканова или @kekekeks. Он является разработчиком этого фреймверка, отлично в разбирается в нем и в dotnet в общем рекомендую!
От себя же скажу что AvaloniaUI – это кросс платформенный фреймверк для построения интерфейсов.
По своей концепции он очень(!) похож на WPF (я перенёс на него уже 3 своих WPF приложения не особо изменяя ккод!). Он быстр и эффективен
, 2d графика в нем рисуется быстрее и потребляет меньше ресурсов, чем у WPF. Также тут есть некоторые плюшки, улучшающие оригинальный WPF. Помните эту войну со стилями и кастомными контролами? Так вот тут можно сделать все немного иначе. Стили могут работать схожим с css способом, что удобно.
Что касается внутреннего устройства то тут применяется библиотека SkeaSharp
для отрисовки графики и GTK (для Unix систем). Также ведётся разработка X11 рендера. Всё это позволяет рисовать интерфейс везде где угодно, даже в буфере консоли. Если бы dotnet core можно было бы запустить в Bios е то avalonia ui отрисовала бы там модный геймерский интерфейс, как на крутых материнских платах (ну вы меня поняли).
AvaloniaUI набирает популярность и является открытым фреймверком. Также хочу сказать что у проекта довольно отзывчивая поддержка и разработчики быстро отвечают на ваши issue
.
Ниже я приведу несколько проектов написанных на этом фреймверке.
- avalon studio
- RoslynPad
- telagram-клиент
- Core2D - редактор схем
- 2D gamr engine
- git client
- Аниме читалка xD
С какими сложностями столкнулся я?
У меня были некоторые сложности с корректной загрузкой картинок в avalon ui. Это произошло из за того что Bitmap в wpf
и Bitmap в avalonia ui
немного отличаются. Но порывшись в документации я понял как это делать.
Вот фрагмент этого кода (вдруг пригодится)
public void Load(string imgFileName, Enums.ImageLoadMode loadMode = Enums.ImageLoadMode.Full)
{
ThreadPool.QueueUserWorkItem(o =>
{
_name = imgFileName;
switch (loadMode)
{
case Enums.ImageLoadMode.Full:
_bitmap = new Bitmap(imgFileName);
break;
case Enums.ImageLoadMode.Miniature:
using (SKStream stream = new SKFileStream(imgFileName))
using (SKBitmap src = SKBitmap.Decode(stream))
{
float scale = 100f / src.Width;
SKBitmap resized = new SKBitmap(
(int)(src.Width * scale),
(int)(src.Height * scale),
src.ColorType,
src.AlphaType);
SKBitmap.Resize(resized, src, SKBitmapResizeMethod.Hamming);
_bitmap = new Bitmap(
resized.ColorType.ToPixelFormat(),
resized.GetPixels(),
new PixelSize(resized.Width, resized.Height),
SkiaPlatform.DefaultDpi,
resized.RowBytes);
}
break;
default:
throw new Exception($"invalid ImageLoadMode:{loadMode.ToString()}");
}
Dispatcher.UIThread.InvokeAsync(() =>
{
_imageBrush.Source = _bitmap;
onLoad?.Invoke();
});
});
}
Также есть некоторые отличия в том как работают биндинги. Вместо INotifyPropertyChanged
используется чуть-чуть другая конструкция: this.RaiseAndSetIfChanged(ref _someVar, value);
.
И наконец мне пришлось написать свой класс RelayCommand
, так как в Net Core нет встроенного:
using System;
using System.Windows.Input;
using RescuerLaApp.Models;
namespace RescuerLaApp.ViewModels
{
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
_raiseCanExecuteChangedAction = RaiseCanExecuteChanged;
SimpleCommandManager.AddRaiseCanExecuteChangedAction(ref _raiseCanExecuteChangedAction);
}
~RelayCommand()
{
RemoveCommand();
}
public void RemoveCommand()
{
SimpleCommandManager.RemoveRaiseCanExecuteChangedAction(_raiseCanExecuteChangedAction);
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute;
}
public void Execute(object parameter)
{
_execute();
SimpleCommandManager.RefreshCommandStates();
}
public bool CanExecute => _canExecute == null || _canExecute();
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
handler?.Invoke(this, new EventArgs());
}
private readonly Action _raiseCanExecuteChangedAction;
public event EventHandler CanExecuteChanged;
}
}
На этом все, остальные не вызвало трудностей и работало также как в wpf.
Погружение в контейнер
Так как моё приложение использует нейроные сети, то для этого как правило нужно установить на компьютер много драйверов и библиотек python, tensorflow, cuda, cudnn… Все это может быть сложным для конечного пользователя (и у поытного). Чтобы избавиться от этих зависимостей было приятно решение использовать Docker контейнер.
Docker контейнер это – программа, позволяющая операционной системе запускать процессы в изолированном окружении на базе специально созданных образов. Несмотря на то, что технологии, лежащие в основа Докера появились до него, именно Докер произвел революцию в том, как сегодня создается инфраструктура проектов, собираются и запускаются сервисы.
К тому же недалеко у github появилась новая функция GitHub Package Registry. Она позволяет скачивать образы docker напрямую, что делает установку и обновление программы ещё проще (вообще про эту функцию и про docker хотелось бы сделать отдельную статью, как и про синтез речи, как и про датафест, но время почему в сутках 24 часа?).
Изначально Docker разрабатывался для его применения на серверах, чтобы у dev-ops было меньше геморроя с зависимости и настройкой сервера. Но умельцы научились запускать в нем и gui приложения. Достаточно только перенаправить вывод X11. Работает как для Linux так и для Windows. По этому если вы хотите запускать Linux программы в Windows – докер вам поможет!
Nvidia-docker.
После появления docker в свет компания nvidia доработала его для использования в докере своих графических карт. При этом на хосоовой машине вовсе не обязательно иметь Cuda и Cudnn. Достаточно обычного драйвера. Это не может не радовать (особенно в моем случае).
К сожалению nvidia docker доступен только для Linux. Буду надеяться что с внедрением подсистемы Linux, Windows возможно тоже получит его поддержку.
Результаты
В результате у меня получилось рабочее приложение которое можно поставить на любую ос. А благодаря docker ещё и избавить пользователя от настройки окружения.

Если вам потнарилось и вы бы хотели поучавствовать в этом проекте то приходите к нам! Мы будем очень рады! Будем двигать open source вместе!
Дополтиельная литература
- ссылка на проект rescuer-la
- avlonia ui оффицифльный сайт
- проекты на github с avalonia ui
- статья про docker
- nvidia-docker
- GUI приложения + docker
- GUI приложения + docker + Windows