Главная » Разработка для Windows Phone 7 » Изменение существующих изображений Windows Phone 7

0

Чтобы изменить существующее изображение, можно вызвать GetData «исходного» Texture2D, изменить полученные пикселы, применяя к ним некоторый алгоритм, и передать полученные значения пикселов в «результирующий» Texture2D, вызвав для него SetData. Это продемонстрировано в проекте RippleEffect (Эффект волны). Исходный Texture2D – это растровое изображение, которое я скопировал со своего сайта. Приложение изменяет его пикселы, создавая эффект прохождения по нему горизонтальных волн:

Исходный («src») и результирующий («dst») объекты Texture2D, а также соответствующие массивы пикселов сохраняются в полях приложения:

Проект XNA: RippleEffect Файл: Game1.cs (фрагмент, демонстрирующий поля)

public class Game1 : Microsoft.Xna.Framework.Game {

GraphicsDeviceManager graphics; SpriteBatch spriteBatch;

const int RIPPLE = 10; Texture2D srcTexture; Texture2D dstTexture; uint[] srcPixels; uint[] dstPixels; Vector2 position;

}

Задается константа, которая показывает, что пикселы исходного растрового изображения будут перемещены вверх и вниз на 10 пикселов. Эта константа используется как в алгоритме,

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

Метод LoadContent загружает srcTexture из содержимого приложения и копирует значения пикселов в массив srcPixels. Результирующий dstTexture на 20 пикселов выше исходного srcTexture. Для результирующих значений пикселов создан массив, но он до сих пор никак не использовался:

Проект XNA: RippleEffect Файл: Game1.cs (фрагмент)

protected override void LoadContent() {

// Создаем новый SpriteBatch, который может использоваться для отрисовки текстур spriteBatch = new SpriteBatch(GraphicsDevice);

srcTexture = this.Content.Load<Texture2D>("PetzoldTattoo"); srcPixels = new uint[srcTexture.Width * srcTexture.Height]; srcTexture.GetData<uint>(srcPixels);

dstTexture = new Texture2D(this.GraphicsDevice,

srcTexture.Width, srcTexture.Height + 2 * RIPPLE); dstPixels = new uint[dstTexture.Width * dstTexture.Height];

Viewport viewport = this.GraphicsDevice.Viewport;

position = new Vector2((viewport.Width – dstTexture.Width) / 2,

(viewport.Height – dstTexture.Height) / 2);

}

Целью метода Update является передача значений пикселов из srcPixels в dstPixels с использованием алгоритма, который включает анимацию. После этого массив dstPixels копируется в dstTexture с помощью метода SetData.

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

•         Последовательный перебор всех строк и столбцов исходного изображения. Получение значений всех пикселов исходного изображения. Для каждого пиксела определение соответствующей строки и столбца в результирующем изображении и сохранение его значения.

•         Последовательный перебор всех строк и столбцов результирующего изображения. Сопоставление строки и столбца с соответствующими строкой и столбцом исходного изображения, получение значения пиксела и сохранение его в результирующем изображении.

В общем случае второй подход несколько сложнее первого, но только он гарантирует задание каждого пиксела результирующего растрового изображения. Поэтому в циклах for следующего метода используются значения xDst and yDst, определяющие столбец и строку результирующего растрового изображения. На их основании вычисляются xSrc and ySrc. (В данном конкретном алгоритме xSrc всегда равен xDst.)

После этого два массива значений пикселов индексируются с использованием переменных dstIndex и srcIndex. Хотя dstIndex всегда будет действительным, потому что основывается на действительных значениях xDst и yDst, некоторые значения srcIndex могут оказаться недействительными. Пикселы, соответствующие недействительным dstIndex, я делаю прозрачными.

Проект XNA: RippleEffect Файл: Game1.cs (фрагмент)

protected override void Update(GameTime gameTime) {

// Обеспечиваем возможность выхода из игры

if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit ();

float phase =

(MathHelper.TwoPi * (float)gameTime.TotalGameTime.TotalSeconds) %

MathHelper.TwoPi;

for (int xDst = 0; xDst < dstTexture.Width; xDst++) {

int xSrc = xDst;

float angle = phase – xDst * MathHelper.TwoPi / 100; int offset = (int)(RIPPLE * Math.Sin(angle));

for (int yDst = 0; yDst < dstTexture.Height; yDst++) {

int dstIndex = yDst * dstTexture.Width + xDst;

int ySrc = yDst – RIPPLE + offset;

int srcIndex = ySrc * dstTexture.Width + xSrc;

if (ySrc < 0 || ySrc >= srcTexture.Height)

dstPixels[dstIndex] = Color.Transparent.PackedValue;

else

dstPixels[dstIndex] = srcPixels[srcIndex];

}

}

this.GraphicsDevice.Textures[0] = null; dstTexture.SetData<uint>(dstPixels);

base.Update(gameTime);

}

В этом перегруженном Update объект srcTexture используется исключительно для определения, не находится ли значение yDst ниже нижней строки растрового изображения. Несомненно, я мог бы сохранить это число строк и удалить само изображение srcTexture.

В результате выполнения перегруженного Update получаем dstTexture, обновленный значениями пикселов из массива dstPixels. Перегруженный Draw просто выводит на экран это изображение:

Проект XNA: RippleEffect Файл: Game1.cs (фрагмент)

protected override void Draw(GameTime gameTime) {

GraphicsDevice.Clear(Color.Navy); spriteBatch.Begin();

spriteBatch.Draw(dstTexture, position, Color.White); spriteBatch.End();

base.Draw(gameTime);

}

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

Однако если значения пикселов вычисляются и передаются при каждом вызове Update, могут возникать проблемы с производительностью. И обработка значений каждого пиксела, и вызов SetData занимают значительное время. Первая версия данного приложения выполнялась нормально на эмуляторе телефона, но на реальном телефоне производительность падала до примерно двух обновлений в секунду. Я уменьшил размеры растрового изображения вдвое (т.е. сократил количество пикселов в 4 раза), чем обеспечил существенное улучшение производительности.

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

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

По теме:

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