Как стать автором
Обновить

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

Как ни странно, самое времяёмким занятием оказалось сделать анимированные иллюстрации. Пришлось цепочку из четырёх разных программ задействовать, чтобы снять видео в GIF, кратно всё увеличить, обрезать и победить искажения палитры (и всё равно в разделе про размытие шрифтов пролезли проблемы с фоном анимашек). :)
ЗЫ: самовар это пасхалка на вариацию чайника Юта в рашн-эдишн?

Да нет, просто мне нравится эта картинка и на ней видны проблемы с пикселями при их наличии. Это работа одного из участников конкурса трехцветных иконок, который я устраивал на gamin.ru: gamin.ru/blog/compo/7028
Статья хороша! Жалко лишь, что не все актуально в современном WPF для WinRT и WP/Silverlight, где часть из описанного просто не работает (тот же BitmapScalingMode), а к основным источникам мыла — не целым координатам — добавляется еще и BitmapCache.
Дело в том, что руководство я написал исходя из собственной практики — с чем в работе не сталкивался тут не представлено. Буду благодарен возможности расширить свой «гербарий». Можете описать проблему с BitmapCache чуть подробнее?
Сначала предыстория. На десктопе в WPF производительность довольно высока, но уже на Atom W8 планшетах или телефонах иногда нужны оптимизации. Свободы выбора в WPF на этих платформах нет никакой — нет шейдеров, управлением сглаживанием, собственных offscreen битмапов и пр. радости., но зато есть и часто используется FrameworkElement.CacheMode = new BitmapCache(); После включения такого режима результат вызова кешируется в текстуру на уровне GPU и текстура эта будет пересчитана при изменении параметров или содержания объекта.

* BitmapCache is the only cache-mode that is supported.
* The Caching is applied to the element and all of it’s child elements.
* BitmapCaching should be used in scenarios where you are blending, transforming (translating, stretching, rotating).
* Misuse of the CacheMode feature can hurt performance, so you need to really think through what you are doing. If your visual tree is interleaving cached and un-cached elements, you are effectively causing multiple rendering surfaces to get created behind the scenes. The un-cached surfaces are rendered in software and the cached surfaces are rendered in hardware. Your performance will be best if you can minimize the total number of rendering surfaces and get the hardware to do work where it can.


Мыльность же возникает в основном при уменьшении картинки. Видимо, у закешированного варианта немного отличаются методы скейлинга (на GPU, а не на CPU сжимается?), или какая-то еще мелочь. Пороюсь в проектах, чтобы найти скриншоты, выложу сюда.
А бороться с этим размытием как? Обновлять кеш после изменения габаритов контрола?
Кеш обновится сам, это все работает прозрачно и без вмешательства. Бороться можно либо целиком отказываясь от кеша в критичном месте, либо следить, чтобы не было масштабирования картинок в принципе. Хорошо хоть на телефонах очень высокий PPI, это для бекграундов не заметно.

P.S. В десктопной Windows 8.1 только что провел пару эксперментов в эмуляторе, мыла не было. Может, дело в отсутствии шейдеров у эмулятора, либо версии ОС. Полноценно потестирую на устройствах после работы.
>>Видимо, у закешированного варианта немного отличаются методы скейлинга
Масштабируется только кэшированный bitmap. В WinRT по окончанию анимации происходит перерисовка bitmap'а.
WPF — это только и исключительно то, что на десктопе. Silverlight и WinRT — это XAML, да, но не WPF.
Мне с трудом представляется раскрытие всех прелестей платформы WPF без XAML. И вообще ваш комментарий какой-то сверх эмоциональный и до абсурда неинформативный.
Никаких эмоций, просто корректировка терминологии. Судя по минусам, я несколько неудачно выразился. Попробую раскрыть смысл.

WPF — это тот фреймворк, который появился первым, и который работает только на десктопе (при этом XAML, несомненно, является его неотъемлимой составной частью). Silverlight — это не WPF. Это обособленный и самодостаточный фреймворк, который также использует XAML. Аналогично, UI-фреймворк в WinRT — это не WPF (официально он называется «WinRT XAML Framework», или просто «XAML Framework»).

Поэтому «современный WPF для WinRT» — это несколько бессмысленная фраза, которая может привести к путанице — особенно, если её читает новичок. Это примерно как сказать про JavaFX, что это «современный Swing для браузеров».
Название «XAML Framework» сложно назвать удачным, потому что XAML — это всего лишь язык разметки, и к гуям он гвоздями не прибит. После таких фраз новички ещё больше запутаются.

«WinRT XAML Framework» — это точно официальное название? А то результатов в Гугле по этому названию практически нет (30 можно считать статистической погрешностью).
Если внимательно посмотреть на статьи на MSDN, то они старательно избегают дать какое-то определенное название всему этому делу. Пару раз мне встречались совсем уж неудобоваримые конструкции вроде «components in namespaces under Windows.UI». В 2011-м, когда все это дело только появилось, я спросил у разработчиков, как называть их детище при обсуждении на StackOverflow и тому подобных местах — и мне сказали, что это «Windows XAML [UI] Framework». На википедии соответствующая статья называется «Windows Runtime XAML Framework», правда, они не говорят, откуда взяли такое название.

Название, действительно, неудачное. Думаю, это прямая калька с пространства имен Windows.UI.Xaml. Но, увы, оно уже установилось — тот же MSDN постоянно говорит о «Store XAML apps» и тому подобных вещах, явно имея в виду далеко не только язык разметки.

Кстати, конкретно в WinRT, XAML к гую намертво еще не прибит, но уже приклеен — XamlReader теперь живет в Windows.UI.Xaml.Markup, и подружить его со своими негуевыми классами не так-то просто — т.е. это можно сделать, если руками реализовать IXamlMetadataProvider и т.д, но это все из разряда «если заведется, то можете ездить, но никаких обещаний». Кастомизация загрузки тоже практически нулевая.
Сначала выкорчёвывали и отделяли, теперь назад пихают… Бардак. Интересно, как они следующий гуёвый фреймворк назовут и в какое пространство имён запихают.

System.Windows.Forms
System.Windows
Windows.UI

Будет просто Windows? %)
Ну тут как раз пертурбации объяснимые :)

System.Windows.Forms — это когда дотнет еще не пытался быть всем для всех, а был просто высокоуровневой платформой для винды, а конкретно Windows Forms — это был по сути порт Windows Foundation Classes из J++. System — знак того, что он является частью стандартной библиотеки (это все еще до попыток стандартизации через ECMA/ISO).

Потом появляется Avalon, то бишь WPF, причем его официальная миссия — стать основным и главным UI-фреймворком под винду. Вплоть до того, что на нем должна быть написана сама винда (тогда еще Longhorn). С другой стороны, он избавился от завязок на Win32 API (всякие там хэндлы и window messages), и, теоретически, может быть кроссплатформенным. Соответственно, System.Windows резервируется под него как the GUI. В итоге авалон из винды выпилили, и лонгхорн стал вистой, но неймспейс остался.

Windows.UI — уже не System, т.е. не декларируется, как часть стандартной переносимой библиотеки .NET, а как чисто виндовый API.
Спасибо за статью. Хороший анализ часто встречающейся проблемы и читается легко.
Очень достойная статья.
Бесценная статья, спасибо!
Спасибо, статья отличная.
Сам давно занимаюсь интеграцией дизайна в XAML, все это уже пройденный этап.
От себя хочу добавить несколько моментов:
1) Использовать рендеринг текста Display — нужно очень аккуратно, тк в этом случае отключается рендер текста и контейнера, где он лежит, видеокартой, что приводит к глюкам и иногда еще большим артефактам чем размытие. Зачастую эффект плавающий.
2) Мы сейчас почти полностью отказались от растровой графики как таковой в XAML интерфейсах.
большинство иконок и графики рисуем в InkSkape и портируем графику и иконки прямо в XAML(объекты Path)
Почему InkSkape — потому что его родной формат SVG, он почти идентичен XAML и InkSkape позволяет рисовать вектор, привязываясь к пикселям сразу — для этого жмем Shift+«3» и видим пиксельную сетку, далее можно включить все привязки. тем самым никогда не попадаем на «полпикселя»
Ну и самый главный лайвхак — в InkSkape есть встроенный XML-Editor (Shift+Ctrl+X) — там просто копируем координаты объектов и вставляем в Data у Path в XAML.


Плоские иконки так делать пара пустяков и благодаря целочисленным координатам они всегда встают идеально в WPF, но можно делать очень сложную, и даже игровую графику, по сути полностью копируя стопку объектов в XAML, но там отдельной статьи материал о том как это делать эффективно.
Чтобы не быть голословным вот видео проекта что у меня сейчас в разработке, вся графика рисовалась в InkSkape и потом XML-Editor + прямыми руками вставлялась в проект и анимировалась. В проекте все векторное на 95%

http://youtu.be/gg8XmABwzkM

Если тема сообществу интересна, как завершу проект, могу написать статейку о процессе.
Хе… Аналогичный случай в нашем колхозе! 100% векторной графики в рабочих проектах. :]

Я пользуюсь open-source утилитой командной строки Svg2Xaml (перед этим приходится переводить всё в контуры, т.к. эта утилита ломает прямоугольники). Честно говоря не знал, что можно просто копированием переносить в XAML.
Svg2Xaml штука все же глючная. В итоге я руками быстрее и качественнее все перевожу, кисти в любом случае в ресурсах, поэтому они мне как правило не нужны, и там проблема с группировкой объектов, из чего вытекает что SVG сначала нужно «подготовить» к экспорту, потом утилита, потом правки в XAML, я же работаю напрямую с исходником и кодом обновременно, поэтому лично для меня мой путь проще.
Совет
InkSkape может координаты представлять как в относительных, так и в «прямых» точках
первые начинаются с маленькой «m» вторые с большой «M», по умолчанию все в относительных, XAML с ними работает, но к сожалению иногда криво, чтобы перевести в «прямые» — выделяем объект и жмем в InkSkape CTRL + "+" — эта комбинация для объединения объектов, но так же она преобразовывает координаты если объект один.
А вообще в настройках выставляем не использовать относительные координаты и радуемся жизни.
Svg2Xaml штука все же глючная

Согласен. Но менее глючных аналогов я не наблюдаю, поэтому как-то притёрся. :) Спасибо за информацию, покопаю про относительные/абсолютные координаты подробнее.

Тема SVG > XAML интересная, явно просится на детальное рассмотрение. Казалось бы сущность WPF провоцирует разработчика перейти на векторные изображения, но при этом это никаких встроенных средств для этого нет. Только кустарные методы и собственные шишки. Было бы здорово, если бы вы поделились своим опытом.
Это верно, когда первый раз видишь Path и примеры от майкрософт, первый вопрос — а как нарисовать что-то свое, на который нет ответа нигде, в примерах уже готовые объекты, Blend — не для рисования, Expression Disign — убог и поддерживает только Canvas и при экспорте — простите, говнокод, а графика как правило — адаптивная.
Мы даже думали над собственным редактором Path на панелях компоновки WPF, но сделать его — полбеды, как на него пересадить дизайнеров и научить работать хотя бы только с цветовыми ресурсами — вот где проблема, а иначе так же как и с Inkscape и Svg2Xaml половину кода руками переписывать.
Inkscape конечно решает большинство проблем, но не все, кое какие рутинные операции приходиться делать вручную. И все же он в разы облегчил работу над исходником дизайна, по крайней мере, там нельзя нарисовать того, чего нельзя «зазамлить» в WPF

У нас сейчас основная проблема, то что в XAML Windows 8 многого, что было раньше, просто нет. Нет триггеров, нет радиальных градиентов, опций рендара… Вот и приходиться иногда извращаться комбинируя растровые объекты с векторными. НО опять же Win8 в плане производительности на пару порядков выше и «мыла» в ней меньше чем в WPF.
Я рисую в Blend без проблем, для интерфейсов хватает на 99%. Делаю сразу не графику на Canvas, а полноценный лейаут.
Из FW или AI импортирую только звездочки-шестеренки.
Вы меня неправильно поняли.
Я имел ввиду создание дизайна в целом, а не только его реализацию в XAML.
Да, все простые элементы с несложным дизайном (кнопки, текстовые блоки, списки и т.п). — бленд для них идеален и с ними все просто, И тут без разницы, где работал дизайнер, я как интегратор повторю все так же в бленде. Мы тут как раз о звездочках и прочих свистелках говорим и о быстрой сборке сложных дизайнов, и о взаимодействии дизайнера с интегратором.
Если дизайнер рисует в AI например, то, чтобы выдрать от туда звездочку-шестеренку, нужно сначала подготовить исходник, а потом как-то экспортировать нужные элементы, потом вставить в бленд, потом причесать XAML(+ресурсы выставить, адаптивность и пр..)
Inkscape позволяет просто открыть исходник как он есть — > Ctrl+ C — > Ctrl+V в бленд, где уже подготовлено все для вставки этой шестеренки., те я выкинул 2 промежуточных шага. При реализации больших проектов и сложных интерфейсов, это существенно
А ещё Inkscape бессовестно бесплатный, в отличии от AI и бленда.
Да, я потом посмотрел — вы рисуете игры. Конечно, для создания игровой графики инструменты Blend недостоточно удобны.

Насчет «промежуточных шагов» не очень понятно. Сохраненый в иллюстраторе файл импортируется в Blend напрямую, без всяких промежуточных шагов. Да, он попадает не в выбранный контейнер, а в корневую директорию. Но если в Blend «всё подготовлено», то какая разница, куда? — Ctrl+ X — > Ctrl+V или взял да перетащил…
Я заранее прошу прощения если не прав. Но не так давно мне нужно было сделать векторную иконку в XAML и установленный InkScape прекрасно экспортировал нарисованное в нем непосредственно в XAML, без каких-либо промежуточных утилит.
К сожалению это происходит так гладко далеко не со всеми картинками.
Так и знал, что не удержишься от комментария =)
Отличная статья!
Хотелось бы по подробнее узнать как в Inkspace рисовать привязанные к пикселям иконки, и экспортировать их в XAML.
Спасибо! :)

Если парой слов: при редактировании делать в Inkskape сетку для привязок в полпикселя шириной. Конвертирую я утилитой командной строки svg2xaml (она больше не поддерживается, но того что есть хватает).
Полпикселя нужны для привяки линий в один пиксель шириной. Перо отсчитывается в обе стороны от неё, поэтому для попадания края линии в границы пикселей она должна проходить точно по середине пикселя. Ну, конечно, привязка к сетке должна быть включена (по умолчанию настройки привязки в вертикальной панели справа). Сетка включается/отключается комбинацией Shift+3 (символ #).

Иллюстрация с настройками сетки

Простите, чуть подправлю: редактор называется Inkscape. Это бесплатный open-source векторный редактор работающий с SVG-изображениями.
Так много замыленных картинок в одной статье я давно не видел! После беглого просмотра, глаза заболели.
Да уж, две трети иллюстраций с качеством ниже среднего. :D

Занятно, что любой борец с чем-либо контактирует с этим гораздо больше остальных. Я в детстве мечтал стать ветеринаром, а потом передумал — поглядел как настоящий ветеринар вытаскивал овчарке клеща из уха. Ветеринар любит счастливых и здоровых животных, а работает преимущественно с больными и насчастными. Работа программиста, как оказалось, в этом плане ничем не отличается.
Статья эпическая, я сам не верстальщик, но два года проработал с xaml верстальщиком плечом плечу и знаю сколько он добивался такого же результата путём проб, ошибок и экспериментов.
Жизнь — боль.
Самый фундаментальный труд из встречавших по теме, спасибо!

Настоящая проблема с устранением мыла возникает при применении к элементам эффекта падающей тени. UseLayoutRounding работает очень избирательно, а когда работает, то поедает закругления мелких радиусов. SnapsToDevicePixels тоже не помогает.
А можно пример в XAML с поеданием углов? Очень любопытно.
Пожалуй, свои слова про поедание углов раундингом возьму назад.
Только что сдублировал попап, чтоб показать разницу… Oбернул парочку WrapPanel, а резкость навелась сама собой. Похоже, что с углами просто не справляется видеокарта моей новой машины при DPI 100%.

saveimg.ru/pictures/24-03-14/c62d79a4aad2051a25a42e54abde6016.png

В-общем, если добавляешь падающую тень, поведение резкости непредсказуемо. Не могу поручиться за .NET 4.51, но в 4.5 я этого победить не мог.
Я могу ошибаться, но на приведённом скрине блоки с надписью «5W» идентичны. При этом они оба и горизонтальная линейка не попадают в границы пикселей по вертикали:



Выше блока полоса в пиксель с размазанным красным, на самом блоке внизу полоса более тёмного красного. На линейке сверху видна полоса размытия (да и снизу тоже, забыл выделить). Явно есть маленький сдвиг вверх относительно точного попадания в пиксели. На горизонтальной линейке ведь нет эффекта тени? Да и, кстати, каким эффектом сделана эта тень?
Эффект — самый обычный DropShadowEffect. Пардон, пример был не очень удачный, вот более наглядный:

saveimg.ru/pictures/25-03-14/2d9405e4b791e39cd23a9cc9eae1cbea.png

Тень применена к попапу, к DataGrid и к маркерам (в данный момент они — красные). При этом кнопки внизу и вверху поплыли, а вот комбобоксы выглядят четко.

Насколько я помню, при применении тени машина обсчитывает объект вместе с его тенью, и то, что размеры и обьекта, и тени задавались в целых пикселях, роли уже не играет — тень может быть обсчитана так, что получится не целое число, и появится мыло либо на всем объекте, либо на каких-то элементах.
Ага, думаю так и есть. Хорошо поплыло, и при этом опять вместе с линейкой. По-идее от этого вполне должны лечить UseLayoutRounding и SnapsToDevicePixels в нужных местах, если это конечно не самостоятельно отрисовывающиеся контролы.
Вот спасибо! А то, честно говоря, это спонтанное замыливание уже задолбало.
Боюсь просто так замыливание не исчезает, придётся попотеть. :)
Ну по крайней мере понятны методы борьбы. А то на одной машине показывает превосходно, а на другой — шлак. И кто виноват неясно. Короче говоря, спасибо за статью.
Статья великолепная, но есть пара существенных замечаний:

1) SnapsToDevicePixels не очень рекомендуется к использованию начиная с .NET 4. Вместо него следует использовать UseLayoutRounding (см тут msdn.microsoft.com/en-us/library/system.windows.frameworkelement.uselayoutrounding%28v=VS.100%29.aspx и тут stackoverflow.com/questions/2399731/when-should-i-use-snapstodevicepixels-in-wpf-4-0).

Чтобы сделать Shape резким, следует использовать RenderOptions.EdgeMode=«Aliased» вместо SnapsToDevicePixels. Это решает проблему размытых векторных иконок в большинстве случаев.

2) Использование Reflection для получения DPI — нехорошо. Правильный способ (CompositionTarget.TransformToDevice): stackoverflow.com/questions/1918877/how-can-i-get-the-dpi-in-wpf

1) Спасибо за ссылки! По второй отличное разъяснение: SnapsToDevicePixels работает на этапе отрисовки, UseLayoutRounding работает на этапе формирования габаритов и разметки. Думаю этого знания достаточно, чтобы выбрать подходящий для ситуации инструмент.

В любом случае, мне видится главным следующий факт — оба эти свойства рекомендательные, каждый контрол их имеет, но не каждый должным образом реализует. Мало того, у них мощные обобщающие названия, эти свойства создают видимость панацеи — выставил и порядок! А это совсем не так. Именно это я пытался донести в тексте руководства.

2) Чем нехорошо использование Reflection для получения DPI? Приведённый «правильный способ» не универсален, т.к. требует для своего применения существующего и отрендеренного визуального контрола. Приходится лезть к родителю или к окну приложения, которое в данный момент может ещё даже не быть отрисованным.

Если брать DIP непосредственно из того места, где он хранится, то данные о разрешении доступны из любого места, например уже в конструкторе вашего самостоятельно отрисовывающегося контрола. Собственно, по ссылке оба метода приведены.
Я имею ввиду что использование Reflection для доступа к приватным членам класса — это грязный хак и это последнее, что следует делать (надо объяснять, почему?). DPI можно получить другими способами, если требуется это сделать в отсутствие загруженного контрола (через WinApi).
Объяснять почему не нужно, я ниже параллельный коммент успел написать. :) Про легальный способ получения DPI не из родительского контрола очень интересно. Как?
Спасибо! Насколько я понял, самый корректный способ — запрашивать через GetDeviceCaps размер рабочего стола и экрана и из их отношения выводить DPI. В остальных случах есть какие-то проблемы на некоторых устройствах, если в манифесте приложения не указано, что оно «dpi aware».
А не указывать dpi awareness — это чем-то плохо?
Видимо тем плохо, что начинает сбоить выяснение реального DPI. Я с этим не сталкивался, но по ссылкам у ребят на планшете Surface Pro и каких-то других штуках проблемы вылезают — на 150% масштабирования шрифтов выдаётся 96dpi вместо ожидаемых 144dpi.
Там, по тем же ссылкам, не исключают, что это некие локальные проблемы, и предлагают дёргать WMI. Мне-то кажется, что если необходимо работать с DPI, то правильно будет указывать dpi awareness.

Ещё вот сохранял себе когда-то ссылочку — High DPI Settings in Windows. Если честно, мне до сих пор так и не довелось столкнуться с dpi, отличным от 96, поэтому я здесь на правах диванного теоретика :)
Э… У нас даже среди коллектива разработчиков некоторые живут в мире увеличенных шрифтов и элементов интерфейса. А уж пользователи старше 50 почти поголовно на таких настройках.
Ну вот недавно приобрёл планшет с Windows 8.1, и это первый мой девайс, на котором пришлось подкрутить DPI. Небольшая утилитка на Windows Forms ожидаемо поплыла :)
Я слукавил, конечно, про Reflection, понятно, что выковыривание приватных членов класса снаружи чревато их исчезновением в какой-то неожиданный момент. Но в данном случае речь про настолько фундаментальный параметр, что встаёт несколько вопросов: почему DPI недоступен как публичный член класса? Почему без Reflection его нужно выковыривать из отрисованных контролов, если разрешение устройства вывода существует вне зависимости от отрисовки контролов?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации