Фигуры и преобразования

123

Поддержка двухмерного рисования — фундамент многих изощренных средств Silverlight, таких как интерактивная графика, анимация и пользовательские элементы управления. Даже если вы не планируете создавать для приложения настраиваемые произведения искусства, вы должны основательно понимать встроенные в Silverlight средства рисования. Вы будете использовать их для добавления в приложение профессиональных эффектов и создания интерактивной графики. Например, фигуры могут изменяться или перемещаться в ответ на действия пользователя.

Платформа Silverlight позаимствовала у WPF целый набор средств рисования - фигуры, преобразования, маски прозрачности и т.д. Эти интерактивные средства аналогичны используемым в WPF и поэтому здесь не рассматриваются. За справкой обратитесь к разделу - Графика и анимация WPF. Ниже будут рассмотрены классы фигур и уникальный для Silverlight, класс перспективных преобразований.

Базовые фигуры

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

Все фигуры являются элементами и наследуют класс FrameworkElement. Отсюда вытекает ряд важных следствий:

Для ускорения прорисовки двухмерных рисунков на основе фигур в Silverlight используются специальные процедуры оптимизации. Например, когда фигуры перекрываются, Silverlight сначала выясняет, какие части фигур невидимы, чтобы не тратить время на их рисование.

Классы фигур

Каждая фигура наследует абстрактный класс System.Windows.Shapes.Shape:

Иерархия классов фигур

На этой диаграмме видно, что в Silverlight определен относительно небольшой набор классов, производных от класса Shape (Фигура). Элементы Line (Отрезок), Ellipse (Эллипс) и Rectangle (Прямоугольник) довольно простые, их назначение очевидно из названия. Элемент Polyline выводит последовательность соединенных отрезков (ломаную линию), a Polygon (Многоугольник) — замкнутую ломаную линию. Класс Path (Контур) — наиболее мощный, он объединяет любые фигуры в одном элементе.

Базовый класс Shape сам по себе ничего не рисует. Он определяет набор важных свойств других фигур:

Свойства фигур, определенные в классе Shape
Свойство Описание
Fill Цвет фона замкнутой фигуры
Stroke Объект кисти, рисующий границу фигуры
StrokeThickness Толщина границы в пикселях
StrokeStartLineCap и StrokeEndLineCap Форма начала и конца отрезка. Эти свойства применимы только к фигурам Line, Polyline и (иногда) Path; все остальные фигуры замкнутые и не имеют начала и конца
StrokeDashArray, StrokeDashOffset и StrokeDashCap Свойства, управляющие выводом штриховой границы фигуры. С их помощью можно изменять длину и шаг штрихов, а также задавать, в каких точках границы начинаются штрихи
StrokeLineJoin и StrokeMiterLimit Свойства, определяющие форму вершин фигуры, т.е. точек, в которых пересекаются отрезки. На фигуры Line и Ellipse эти свойства не влияют
Stretch Свойство, определяющее заполнение фигурой доступного пространства. Его можно использовать, например, для создания фигуры, расширяющейся до размеров, позволяемых контейнером
GeometryTransform Свойство, позволяющее применить объект преобразования, который изменяет систему координат фигуры. С помощью этого свойства можно наклонять, поворачивать или смещать фигуру. Объекты преобразований особенно полезны в анимации

Перспективные преобразования

В Silverlight версии ниже 5 нет полнофункционального набора инструментов для трехмерного рисования, но есть средства перспективных преобразований, позволяющие имитировать трехмерные поверхности. Как и при обычном преобразовании, объект перспективного преобразования манипулирует внешним видом существующего двухмерного элемента, проецируя его на повернутую поверхность. В результате манипулирования элемент выглядит так, будто находится на трехмерной поверхности.

Перспективные преобразования весьма полезны, однако им далеко до реальных трехмерных моделей. Их главный, самый очевидный, недостаток состоит в том, что преобразованию подвергается только одна фигура — плоский прямоугольник, на котором размещены элементы. Сравните: в действительно трехмерных моделях для построения сложных поверхностей используются крошечные совмещаемые треугольники. Для определения их координат и освещенности применяются сложные математические формулы, требующие огромного объема вычислений.

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

В Silverlight определен абстрактный класс Transform, наследуемый всеми классами двухмерных преобразований. Аналогично этому, все классы проецирования наследуют абстрактный класс System.Windows.Media.Projection. В настоящий момент Silverlight поддерживает два класса проецирования: PlaneProjection и намного более сложный Matrix3DProjection, в котором преобразования определяются с помощью трехмерных матриц.

Класс PlaneProjection позволяет поворачивать и смещать плоское изображение в трехмерном пространстве. Трехмерную плоскость можно повернуть вокруг оси X, Y или Z. На рисунке ниже показан поворот плоского изображения на 45° вокруг разных осей:

Поворот изображения с помощью класса PlaneProjection

На этом рисунке элемент повернут вокруг своей центральной точки. С помощью свойств объекта PlaneProjection можно настроить параметры поворота.

Свойство RotationX определяет угол поворота вокруг оси Х. Свойство CenterOfRotationX содержит координату X центра поворота в относительных единицах. При значении 0 центр находится в крайней левой точке, 1 — крайней правой, 0.5 — посредине (это значение установлено по умолчанию). Поворот относительно других осей определяют аналогичные свойства.

Во многих случаях свойства поворота — единственное, что вам нужно будет изменить при использовании объекта PlaneProjection. Однако элемент, кроме этого, можно еще и сместить. Существуют два способа смещения:

Когда элемент еще не повернут, глобальные и локальные свойства имеют одинаковые значения. Увеличение значений GlobalOffsetX или LocalOffsetX приведет к смещению элемента вправо.

Предположим, объект был повернут вокруг оси Y с помощью свойства RotationY. Теперь увеличение значения GlobalOffsetX приведет к смещению отображенного содержимого вправо (как и в предыдущем случае, когда элемент не был повернут), однако увеличение значения LocalOffsetX приведет к смещению не точно вправо, а вдоль новой оси X, которая теперь указывает новое трехмерное направление. В результате содержимое будет смещено вправо и вверх.

Операцию проецирования можно применить почти к любому элементу Silverlight, потому что каждый класс, производный от UIElement, имеет необходимое для этого свойство Projection. Чтобы получить для элемента эффект перспективы, нужно создать объект PlaneProjection и присвоить его свойству Projection в коде или разметке XAML.

Ниже приведена разметка, поворачивающая среднее изображение на приведенном выше рисунке:

<Grid Background="SlateGray">
        <Border BorderBrush="#555" Background="White" BorderThickness="4" Margin="5">
            <Border.Projection>
                <PlaneProjection RotationY="45"/>
            </Border.Projection>
            <Path Data="F1 M 40,35L 40,18L 37,18L 37,19L 36,19L 36,28L 34,28L 34,26L 31.0209,26L 31,24L 30.5,14.5L 29,14L 27.5,9.00001L 
              26,14L 24.5,14.5L 24,24L 24,28L 23,28L 23,26L 19,26L 19,30L 17,30L 17,20L 11,20L 11,28L 9,28L 9,27L 8,27L 7.99999,35L 40,35 Z"
              Stroke="#fbac14" Stretch="Fill" Fill="#fbac14"
              />
        </Border>
</Grid>

Как и обычное, перспективное преобразование выполняется после размещения элементов. На рисунке этот факт проиллюстрирован с помощью закрашенных областей, расположенных в исходной позиции. Элементы получили новые позиции, однако области с закрашенным фоном по-прежнему используются для вычисления позиций. Выполняется правило, применимое почти ко всем элементам: если элементы перекрываются, поверх других выводится элемент, объявленный последним (некоторые элементы, например Canvas, подчиняются не этому правилу, а значению свойства ZIndex).

Чтобы лучше понять, как взаимодействуют свойства объекта PlaneProjection, поэкспериментируйте с простым тестовым приложением, в котором с помощью ползунков пользователь может поворачивать элемент вокруг осей X, Y и Z. Кроме того, элемент можно сместить локально или глобально вдоль оси Х с помощью свойств LocalOffsetX и GlobalOffsetX, описанных выше:

<Grid Background="#888">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid x:Name="transformGrid">
            <Border Width="300" Height="200" BorderBrush="#444" BorderThickness="4">
                <Border.Projection>
                    <PlaneProjection x:Name="planeProjection"/>
                </Border.Projection>
                <StackPanel>
                    <TextBlock FontSize="16" Text="Введите данные:" Margin="10,5"/>
                    <TextBox Margin="10,5" />
                    <Button Padding="5" Margin="0,5" Content="OK" HorizontalAlignment="Center" Width="80"/>
                    <Image Source="super_man.png" Width="64" Height="64"/>
                </StackPanel>
            </Border>
        </Grid>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <TextBlock Text="RotationX" FontSize="14" Margin="6"/>
            <Slider Grid.Column="1" Minimum="-180" Maximum="180" Margin="6"
                    Value="{Binding ElementName=planeProjection, Path=RotationX, Mode=TwoWay}"/>
            <TextBlock Text="RotationY" Grid.Row="1" FontSize="14" Margin="6"/>
            <Slider Grid.Column="1" Grid.Row="1" Minimum="-180" Maximum="180" Margin="6"
                    Value="{Binding ElementName=planeProjection, Path=RotationY, Mode=TwoWay}"/>
            <TextBlock Text="RotationZ" FontSize="14" Grid.Row="2" Margin="6"/>
            <Slider Grid.Column="1" Minimum="-180" Maximum="180" Grid.Row="2" Margin="6"
                    Value="{Binding ElementName=planeProjection, Path=RotationZ, Mode=TwoWay}"/>
            <StackPanel Margin="6" HorizontalAlignment="Center" Orientation="Horizontal" Grid.Row="3" Grid.ColumnSpan="2">
                <Button x:Name="GlobalOffsetXUp" Content="+GlobalOffsetX" Margin="3" Padding="5" Click="cmdClick" />
                <Button x:Name="GlobalOffsetXDown" Content="-GlobalOffsetX" Margin="3" Padding="5" Click="cmdClick" />
                <Button x:Name="LocalOffsetXUp" Content="+LocalOffsetX" Margin="3" Padding="5" Click="cmdClick" />
                <Button x:Name="LocalOffsetXDown" Content="+LocalOffsetX" Margin="3" Padding="5" Click="cmdClick" />
            </StackPanel>
        </Grid>
</Grid>
private void  cmdClick(object sender, RoutedEventArgs e)
{
       switch (((Button)e.OriginalSource).Name)
       {
                case "GlobalOffsetXUp":
                    planeProjection.GlobalOffsetX += 10;
                    break;
                case "GlobalOffsetXDown":
                    planeProjection.GlobalOffsetX -= 10;
                    break;
                case "LocalOffsetXUp":
                    planeProjection.LocalOffsetX += 10;
                    break;
                case "LocalOffsetXDown":
                    planeProjection.LocalOffsetX -= 10;
                    break;
       }
}
Поворот и смещение в трехмерном пространстве

Проецирование можно применить к любому элементу. Эта операция часто полезна, когда необходимо разместить в контейнере больше элементов. Приведенный выше пример интересен тем, что проецируемые элементы (кнопка и текстовое поле) и в повернутом состоянии продолжают взаимодействовать стандартным образом: реагируют на щелчки мыши, получают фокус ввода и т.п.

Пройди тесты
Лучший чат для C# программистов