Создании эффектов пламени пожара и горения свечи

На главную страницу

Еще раз о создании эффектов пламени пожара и горения свечи

Аннотация: Когда в программу "MediaViewer Look Mona Lisa" мне срочно захотелось добавить еще один светомузыкальный эффект, я вспомнил, как в Dos Navigator в качестве одного из Screen Saver был красивый эффект пламени. Желая его повторить - облазил инет, но на C# нашел только один источник Imran Nazar "Putting the effect on-screen". Код был приемлем, но эффект явно проигрывал тому, что осталось в памяти. Поэтому, забыв о срочности, я стал экспериментировать с кодом, и, помимо того, что в указанном коде обнаружилась досадная ошибка, мне удалось определить то, как можно влиять на параметры пламени и один и тот же код использовать как для имитации пожара, так и для имитации горения свечи. Этому и посвящена данная статья.


В начало

1. Эффект пламени

Алгоритм создания эффекта пламени на экране монитора известен со времен незабвенной DOS. Он основан на создании специальной палитры (256 цветов), включающей цвета переходящие от белого до черного. Этот переход должен плавно пройти через желтый и красный цвета. Источник огня предполагается внизу. Для этого, на нижней линии рисунка помещается линия, состоящая из точек со случайными значениями цветов (из набора созданной палитры). Далее производится расчет цвета точек, начиная с точки начала верхней линии по алгоритму усреднения цвета окружающих данную точку слева, справа и снизу. Присвоение цвету точки усредненного значения, где сверху преобладает черный цвет и, в начале, только на нижней линии он может быть более светлым, позволяет при первом проходе по рисунку несколько изменить цвет только второй линии снизу. Каждый следующий проход добавляет светлые линии, которые становятся все темнее в направлении вверх. К верхним линиям он успевает достичь черного цвета. Результат - эффект пламени.

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

И так, мы хотим создать пламя, показанное на Рис.1.

Рис.1. Пламя

Для этого, создаем проект решения и помещаем на форму контрол PictureBox, свойство которого Dock установим в Fill, а Size Mode в StretchImage. Поместим в решение контрол Timer, установив его свойства Enablet в True, Interval в 10. По собственному усмотрению можно добавить меню и другие прибамбасы, которые показаны на рисунке, но не относятся к теме статьи.

Начнем создание кода.

Переменные, которые нам понадобятся:

//Рабочий рисунок
private Bitmap workBmp = null;
private Random rnd = new Random(DateTime.Now.Millisecond);
private int viWidth = 320;
private int viHeight = 180;

Напомним, что viWidth и viHeight - размеры холста. При уменьшении viHeight пламя полностью захватит весь рисунок, при увеличении - занимает только нижнюю часть. Выбранный размер соответствует эффекту Рис.1.

При загрузке программы создаем палитру, по описанному выше алгоритму.

private void Form1_Load(object sender, EventArgs e)
{
 vStartFlame();
}
#region vStartFlame() 
private void vStartFlame()
{
 workBmp = new Bitmap(viWidth, viHeight, PixelFormat.Format8bppIndexed);
 ColorPalette pal = bufBmp.Palette;         
 for (int i = 0; i < 64; i++)
 {
  pal.Entries[i] = Color.FromArgb(i << 2, 0, 0);
  pal.Entries[i + 64] = Color.FromArgb(255, i << 2, 0);
  pal.Entries[i + 128] = Color.FromArgb(255, 255, i << 2);                
  //Попытка увеличить белую составляющую
  pal.Entries[i + 192] = Color.FromArgb(255, 255, 255);
 }
 workBmp.Palette = pal;
}
#endregion

Можно еще сдвинуть палитру в сторону белого, например, так (использовано для эффекта Рис.1.):

....
for (int i = 0; i < 240; i += 16)
{
 pal.Entries[i] = pal.Entries[i + 1];
 pal.Entries[i + 1] = pal.Entries[i + 2];
 pal.Entries[i + 2] = pal.Entries[i + 3];
 pal.Entries[i + 3] = pal.Entries[i + 4];
 pal.Entries[i + 4] = pal.Entries[i + 5];
 pal.Entries[i + 5] = pal.Entries[i + 6];
 pal.Entries[i + 6] = pal.Entries[i + 7];
 pal.Entries[i + 7] = pal.Entries[i + 8];
 pal.Entries[i + 8] = pal.Entries[i + 9];
 pal.Entries[i + 9] = pal.Entries[i + 10];
 pal.Entries[i + 10] = pal.Entries[i + 11];
 pal.Entries[i + 11] = pal.Entries[i + 12];
 pal.Entries[i + 12] = pal.Entries[i + 13];
 pal.Entries[i + 13] = pal.Entries[i + 14];
 pal.Entries[i + 14] = pal.Entries[i + 15];
 pal.Entries[i + 15] = pal.Entries[i + 16];
}
workBmp.Palette = pal;

И, собственно код отрисовки пламени:

private void timer1_Tick(object sender, EventArgs e)
{
 //Прямая работа с памятью - не забудем в свойствах проекта на 
 //закладке Build поставить галочку Allow unsafe code.
 unsafe
 {
  BitmapData bmpLook = workBmp.LockBits(
    new Rectangle(Point.Empty, workBmp.Size),
    ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
   //bufdata - указатель на левую верхнюю точку рисунка
   Byte* pointtop = (Byte*)bmpLook.Scan0;
   //pointbottom - указатель на точку начала нижней линии рисунка
   Byte* pointbottom = pointtop + ((viHeight - 1) * viWidth);
   Byte* i = null; 
   int j = 0;
   //Заполняем нижнюю линию точками, со случайно выбранными цветами
   for (int x = 0; x < viWidth; x++)
   {
     *(pointbottom + x) = (Byte)rnd.Next(0,255);
   }
   //Заполняем остальные точки, значениями усредненными по алгоритму
   for (i = pointtop; i < bufbottom; i++)
   {                                     
    if (i != pointtop)
    {
      //Вес точки снизу увеличен до 3х
      j = *(i - 1) + *(i + 1) + *(i + viWidth) * 3;
      //Усредняем
      j /= 5;
      if (j < 0) j = 0;
      *i = (Byte)j;                        
     }
    }
   }
   //Разблокируем память
   workBmp.UnlockBits(bmpLook);
   bmpLook = null;
   //Два вспомогательных Bitmap для переноса изображения 
   //в PictureBox и для удаления ~ 4 pix из нижней части
   //Убирает нежелательный мусор при растяжении рисунка в 
   //PictureBox
   Bitmap bmpA = new Bitmap(viWidth, viHeight);
   bmpA = workBmp;
   Bitmap bmpB = new Bitmap(viWidth, viHeight - 4);
   Graphics grtmpB = Graphics.FromImage(bmpB);
   grtmpB.DrawImage(bmpA, 0, 0);            
   grtmpB.Dispose();
   pictureBox1.Image = bmpB;
   bmpB = null;
   bmpA = null;
 }
}

На Рис.1. использовано заполнение нижней линии, также со сдвигом в сторону белого:

for (int x = 0; x < viWidth; x++)
{
  *(pointbottom + x) = (Byte)rnd.Next(64,255);
}

И последнее, с чем можно поэкспериментировать - это вес точки снизу в алгоритме усреднения. Увеличивать его более 25 не имеет смысла из за искажений внутренней части пламени.


В начало

2. Эффект горения свечи

Внесем незначительные изменения в проект решения, который мы использовали выше. Свойство SizeMode у контрола PictureBox установим в CenterImage и изменим размеры нашего холста:

int viWidth = 100;
int viHeight = 180;

Оставим без изменения функцию vStartFlame (с учетом описанной добавки сдвига палитры в сторону белого).

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

for (int x = viWidth / 3; x < viWidth - viWidth/3; x++)
{
  *(pointbottom + x) = (Byte)rnd.Next(100,255);
}

И, в отличие от пламени, огонь должен переходить в черное, не только сверху, но и с левой и правой сторон. Поэтому потребуется брать более точек для усреднения слева и справа, например, так:

for (i = pointtop; i < pointbottom; i++)
{
 if (i > pointtop + 2 && i < pointbottom-2)
 {
   j = *(i - 1) + *(i - 2) + *(i + 1) + *(i + 2) +
            *(i + viWidth)*5;
    j /= 9;
    if (j < 0) j = 0;
    *i = (Byte)j;                        
  }
}

Результат показан на Рис.2.

Здесь, как и при создании эфекта пламени, широкое поле для эксперементов.

Рис.2. Горение свечи


В начало

Литература

Молчанов Владислав 15.08.2009г.

Еcли Вы пришли с поискового сервера - посетите мою главную страничку

На главной странице Ва узнаете, как я могу помочь Вам в написании офисной программы любой сложности в соответствии с Вашими запросами. И найдете ряд програм или ссылок на образцы.

Кроме того - на главной странице Вы найдете бесплатные программы: "MediaViewer Look Mona Lisa" - Viewer, позволяющий просмотривать Видео файлы, прослушивать мультимедийные файлы, и просмотривать файлы рисунков большинства известных форматов, программы комплекса Veles - программы для автолюбителей, программы из раздела графика - программы для работы с фото, сделанными цифровым фотоаппаратом, программу Bricks - игрушку для детей и взрослых, программу записную книжку, программу TellMe - говорящий Русско-Английский разговорник - программу для тех, кто собирается погостить за бугром или повысить свои знания в английском, теоретический материал по программированию в среде Borland C++ Builder, C# (Windows приложения и ASP.Net Web сайты) и многое другое.

logo.gif

В начало

В начало книги

На главную страницу


Сайт управляется системой uCoz