Главная » Разработка для Windows Phone 7 » Векторная графика в растровой матрице Windows Phone 7

0

WriteableBitmap позволяет сочетать два подхода создания изображений. В следующем примере WriteableBitmap обеспечивает отображение Path поверх градиента, использующего прозрачность, так что мы можем увидеть предварительно умноженные альфа в действии.

Несомненно, вы помните этот элемент Path из предыдущей главы, который задавался строкой Silverlight Path Markup Syntax и обеспечивал отрисовку кота. Задача приложения VectorToRaster (Вектор в растр) – создать растровую матрицу точно такого же размера, как этот кот, и затем поместить в нее изображение кота.

Изображение кота описывается с помощью Path Markup Syntax в элементе Path в разделе Resources файла MainPage.xaml:

Проект Silverlight: VectorToRaster Файл: MainPage.xaml (фрагмент)

<phone:PhoneApplicationPage.Resources> <Path x:Key="catPath"

Data="M 160 140 L 150 50 220 103 M 320 140 L 330 50 260 103 M 215 230 L 40 200 M 215 240 L 40 240 M 215 250 L 40 280 M 265 230 L 440 200 M 265 240 L 440 240 M 265 250 L 440 280 M 240 100 A 100 100 0 0 1 240 300 A 100 100 0 0 1 240 100 M 180 170 A 40 40 0 0 1 220 170 A 40 40 0 0 1 180 170 M 300 170 A 40 40 0 0 1 260 170

A 40 40 0 0 1 300 170" /> </phone:PhoneApplicationPage.Resources>

PathGeometry описывается в XAML в коллекции Resources. Я хотел определить PathGeometry напрямую, без применения Path, но как я ни старался и что бы я ни делал – задавал строку синтаксиса Path Markup Syntax как свойство Figures объекта PathGeometry либо помещал строку между открывающим и закрывающим тегами PathGeometry – у меня ничего не получилось.

Я использую элемент Path исключительно, чтобы обозначить эту строку как Path Markup Syntax для синтаксического анализатора XAML. Элемент Path не будет использоваться для каких-либо иных целей в приложении.

Область содержимого включает только пустой элемент Image для размещения растрового изображения:

Проект Silverlight: VectorToRaster Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Image Name="img"

HorizontalAlignment="Center" VerticalAlignment="Center" />

</Grid>

Все остальное происходит в конструкторе класса MainPage. Он немного длинноват, но снабжен подробными комментариями. Мы поэтапно рассмотрим всю логику:

Проект Silverlight: VectorToRaster Файл: MainPage.xaml.cs (фрагмент)

public MainPage() {

InitializeComponent();

// Получаем PathGeometry из ресурса Path catPath = this.Resources["catPath"] as Path; PathGeometry pathGeometry = catPath.Data as PathGeometry; catPath.Data = null;

// Получаем границы геометрического элемента Rect bounds = pathGeometry.Bounds;

// Создаем новый контур, визуальное представление которого // будет сформировано в растровой матрице

Path newPath = new Path {

Stroke = this.Resources["PhoneForegroundBrush"] as Brush, StrokeThickness = 5, Data = pathGeometry

// Создаем WriteableBitmap WriteableBitmap writeableBitmap =

new WriteableBitmap((int)(bounds.Width + newPath.StrokeThickness),

(int)(bounds.Height + newPath.StrokeThickness));

// Задаем цвет фона растрового изображения

Color baseColor = (Color)this.Resources["PhoneAccentColor"];

// Рассматриваем растровое изображение как эллипс: // radiusX и radiusY – тоже центры!

double radiusX = writeableBitmap.PixelWidth / 2.0; double radiusY = writeableBitmap.PixelHeight / 2.0;

for (int y = 0; y < writeableBitmap.PixelHeight; y++)

for (int x = 0; x < writeableBitmap.PixelWidth; x++) {

double angle = Math.Atan2(y – radiusY, x – radiusX); double ellipseX = radiusX * (1 + Math.Cos(angle)); double ellipseY = radiusY * (1 + Math.Sin(angle));

double ellipseToCenter =

Math.Sqrt(Math.Pow(ellipseX – radiusX, 2) + Math.Pow(ellipseY – radiusY, 2));

double pointToCenter =

Math.Sqrt(Math.Pow(x – radiusX, 2) + Math.Pow(y – radiusY, 2));

double opacity = Math.Min(1, pointToCenter / ellipseToCenter);

byte A =              (byte)(opacity  * 255);

byte R =              (byte)(opacity  * baseColor.R);

byte G =              (byte)(opacity  * baseColor.G);

byte B =              (byte)(opacity  * baseColor.B);

int color = A << 24 | R << 16 | G << 8 | B;

writeableBitmap.Pixels[y * writeableBitmap.PixelWidth + x] = color;

}

writeableBitmap.Invalidate();

// Находим трансформацию для перемещения Path к краям

TranslateTransform translate = new TranslateTransform {

X = -bounds.X + newPath.StrokeThickness / 2,

Y = -bounds.Y + newPath.StrokeThickness / 2

writeableBitmap.Render(newPath, translate); writeableBitmap.Invalidate();

// Задаем растровое изображение как значение элемента Image img.Source = writeableBitmap;

}

Код начинается с извлечения PathGeometry из коллекции Resources. Поскольку PathGeometry прикрепляется к элементу Path, он не сможет использоваться для каких-либо иных целей. Поэтому свойству Data элемента Path задается значение null. На этом роль элемента Path заканчивается, и он больше не используется в данном приложении.

Свойство Bounds, определенное классом Geometry, возвращает объект Rect с координатами верхнего левого угла объекта PathGeometry, в данном случае это точка (40, 50), и его шириной и высотой, которые в данном случае равны 400 и 250, соответственно. Обратите внимание, что данные значения строго геометрические и не учитывают ширину обводки, которая может присутствовать при формировании визуального представления этого элемента.

Далее для этого геометрического элемента создается элемент Path. В отличие от элемента Path в коллекции Resources XAML-файла, для данного Path задана кисть Stroke толщиной (StrokeThickness) 5.

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

Прежде чем формировать визуальное представление Path в WriteableBitmap, я хочу задать для растровой матрицы градиентную кисть, прозрачную в центре и переходящую в контрастный цвет по краям:

Color baseColor = (Color)this.Resources["PhoneAccentColor"];

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

Здесь два вложенных цикла for для x и y обеспечивают перебор всех пикселов растровой матрицы. Для каждого пиксела вычисляется значение opacity, диапазон допустимых значений которого от 0 (прозрачный) до 1 (непрозрачный):

double opacity = Math.Min(1, pointToCenter / ellipseToCenter);

Это значение opacity используется не только для вычисления байта альфа-канала, но также как коэффициент предварительного умножения для значений красного, зеленого и синего каналов:

byte A = (byte)(opacity * 255); byte R = (byte)(opacity * baseColor.R); byte G = (byte)(opacity * baseColor.G); byte B = (byte)(opacity * baseColor.B);

Осталось лишь свести вместе все компоненты цвета и проиндексировать массив Pixels:

int color = A << 24 | R << 16 | G << 8 | B;

writeableBitmap.Pixels[y * writeableBitmap.PixelWidth + x] = color;

На данный момент приложение обработало все элементы массива Pixels, поэтому пора обновить изображение:

writeableBitmap.Invalidate();

Теперь в растровой матрице должно быть сформировано визуальное представление элемента Path под именем newPath (Новый контур). Для элемента Path предусмотрен объект PathGeometry, верхний левый угол которого располагается в точке (40, 50), но при задании размеров WriteableBitmap использовались только ширина и высота геометрического элемента без учета толщины обводки. При формировании визуального представления Path во WriteableBitmap трансформация TranslateTransform должна обеспечить смещение верхнего левого угла прямоугольника на значения X и Y, полученные из свойства Bounds объекта PathGeometry. Но после этого необходимо также сместить Path немного вправо и вниз, чтобы вместить толщину обводки:

TranslateTransform translate = new TranslateTransform {

X = -bounds.X + newPath.StrokeThickness / 2, Y = -bounds.Y + newPath.StrokeThickness / 2

Теперь визуальное представление элемента Path во WriteableBitmap может быть сформировано:

writeableBitmap.Render(newPath, translate); writeableBitmap.Invalidate();

И вот результат:

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

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

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