Fire 5

Burn it! (2)

Hello all!

This post is no more that a little optimization of Burn it! post, where we show the 90’s popular effect of Fire rising up. This optimization it’s no more than work only with 256 colors (so reducing all to bytes), not use the zoom feature, and work directly with arrays & in unsafe memory context. As well, we use 8bpp indexed format for the generated bitmap, so this way, the array we use to calculate (Fore array) will be copied directly to bitmap, and as well, we will copy palette generated to that bitmap. This way it’s the way we realize this effect at famous mode 0x13, being video memory indexed at this resolution same way that it’s this bitmap. As well, due this bitmap format it’s smaller (got 256 x 3 bytes for palette, and then width*height bytes for each indexed pixel, instead of the width*height*3 bytes of another formats) transmission from server to client browser it’s faster as well. As told in first post, the fact to use server to generate the images it’s only due teaching & showing purposes for the effect itself: performance it’s not the best. The best way it’s that this effect to be generated directly in client’s side, using Javascript. This will be covered when possible in a new post about. As well, you can try this effect in desktop applications with almost no modifications, when you can observe then the more fast movement.

Please check out as well monogame version for a desktop app

Source code (about the same but with little modifications) are at the end of this post.

So, here it is FireEffect.aspx.cs

  public partial class FireEffect : System.Web.UI.Page
 {
     private const int _WIDTH = 100;   // 'heat' array size 
     private const int _HEIGHT = 300; // 
 
     private const int _FIREBASELENGTH = 5; // base line length 
     private const int _MAXCOLORS = 255; // Maximum palette colors
 
     private static     Bitmap BShow = new Bitmap(    _WIDTH,
                                                 _HEIGHT,
                                                  System.Drawing.Imaging.PixelFormat.Format8bppIndexed); // projection bitmap
 
     private static byte[] Back = new byte[_WIDTH*_HEIGHT];
     private static byte[] Fore = new byte[_WIDTH*_HEIGHT];
 
     private static Random r = new Random (); // Working random object
     private static Color[] Palette = new Color[_MAXCOLORS];
 
     public void Page_Load(object sender, EventArgs args)
     {
         // First time page load, lets generate a palette & put fire on base 
         if (!Page.IsPostBack) 
         {
             btRandomPalette_Click (null, null);
             FireBase ();
         }
     }
 
     protected void Gradient (int ao, int af)
     {
         int d = af - ao; // total distance between colors
 
         float 
         ro = Palette [ao].R, // Initial r,g,b values
         go = Palette [ao].G,
         bo = Palette [ao].B,
         dr = (Palette[af].R - ro)/d, // diferential of r,g,b
         dg = (Palette[af].G - go)/d, 
         db = (Palette[af].B - bo)/d;
 
         // lets fill each color in palette between range
         for (int i=0;i<d+1;i++)
             Palette [i + ao] = 
                 Color.FromArgb (
                     (byte)(ro + i*dr), 
                     (byte)(go + i*dg), 
                     (byte)(bo + i*db)
                 );
     }
 
     protected void FillGradients(Color[] C)
     {
         // counting the back color, 0 indexed, there will be C.Length + 1 color turns. 
         int m = _MAXCOLORS / (C.Length+1);
 
         // first color (last one, 0, 'coldest' color) will be back page color
         Palette[0] = ColorTranslator.FromHtml(PageBody.Attributes["bgcolor"]);
 
         // Lets point each choosen color at position and do gradient between them
         for (int i = 1; i < C.Length+1; i++) 
         {
             Palette [i * m] = C [i-1];
             Gradient ((i - 1) * m, i * m);
         }
     }
 
     public void btRandomPalette_Click (object sender, EventArgs args)
     {
         Color[] C = new Color[6];
             
         for (int i = 0; i < C.Length; i++) 
             C[i] = Color.FromArgb (r.Next () % 0xff, r.Next () % 0xff, r.Next () % 0xff);
 
         FillGradients (C);
         DumpPalette ();
     }
 
     public void btDefaultPalette_Click (object sender, EventArgs args)
     {
         Color[] C = new Color[6];
 
         C [0] = Color.Navy;
         C [1] = Color.OrangeRed;
         C [2] = Color.Orange;
         C [3] = Color.Yellow;
         C [4] = Color.White;
         C [5] = Color.White;
 
         FillGradients (C);
         DumpPalette ();
     }
 
     private void DumpPalette()
     {
         ColorPalette P = BShow.Palette;
 
         for (int i = 0; i < _MAXCOLORS; i++)
             P.Entries [i] = Palette[i];
 
         BShow.Palette = P;
     }
 
     protected void Burn()
     {
         int x, y;
         int c;
 
         for (y = 1; y < _HEIGHT - 1; y++) 
             for (x = 1; x < _WIDTH - 1; x++) 
             {
                 // Get all surrounding color indexs and do mean...
                 c = (Back [x - 1 + (y - 1)*_WIDTH] + Back [x + (y - 1)*_WIDTH] + Back [x + 1 + (y - 1)*_WIDTH] +
                     Back [x - 1 + y*_WIDTH]  +  /*   (x,y)    */     Back [x + 1 + y*_WIDTH]     + 
                     Back [x - 1 + (y + 1)*_WIDTH] + Back [x + (y + 1)*_WIDTH] + Back [x + 1 + (y + 1)*_WIDTH])
                     >> 3;
 
                 // ...and we put it in the upper pixel. And then, fire grows...
                 Fore [x + (y - 1)*_WIDTH] = (byte)c;
             }
     }
         
     protected void FireBase()
     {
         byte c = (byte)(_MAXCOLORS-1);
         int x, y;
 
         // Lets do, at image base, some random color lines
         for (y = _HEIGHT - 3; y < _HEIGHT; y++)
             for (x = 0; x < _WIDTH; x ++) 
             {
                 if (x % _FIREBASELENGTH == 0)
                     c = (byte)(Back [x + y*_WIDTH] + r.Next () % _MAXCOLORS);
 
                 Back [x + y*_WIDTH] = (byte)(c % _MAXCOLORS - 1);
             }
     }
 
     protected void DumpToBitmap(Bitmap BShow)
     {
         Rectangle R = new Rectangle (0, 0, _WIDTH, _HEIGHT);
         BitmapData b = BShow.LockBits(R,ImageLockMode.WriteOnly,System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
     
         IntPtr ptr = b.Scan0;
         Marshal.Copy (Fore, 0, ptr, _WIDTH * _HEIGHT);
         BShow.UnlockBits (b);
 
         // When Fore array its all projected to bitmap, lets copy it as well to back, 
         // as source for next Burn 
         Array.Copy(Fore,Back,_WIDTH * _HEIGHT);
     }
 
     protected void Timer_Tick(object sender, EventArgs e)
     {
         // Counter
         Stopwatch sw = Stopwatch.StartNew();
 
         // Burn step 
         Burn ();
         sw.Stop ();
 
         // Dump it to bitmap
         DumpToBitmap (BShow);
 
         // Put fuel
         FireBase ();
         InfoLabel.Text = "Bitmap generated on " + sw.ElapsedMilliseconds.ToString () + "ms";
 
         // Dump to a string
         sw.Start ();
         using (MemoryStream m = new MemoryStream ()) 
         {
             BShow.Save (m, System.Drawing.Imaging.ImageFormat.Jpeg);
             UImage.ImageUrl = "data:image/Jpeg;base64," + Convert.ToBase64String (m.ToArray ());
         }
         sw.Stop ();
         InfoLabel.Text += " / total ellapsed time : " + sw.ElapsedMilliseconds.ToString() + "ms";
     }
 }

And here, monogame version of it :

 using System;
 using System.Drawing;
 using System.Drawing.Imaging;
 using System.IO;
 using System.Runtime.InteropServices;
 using Microsoft.Xna.Framework;
 using Microsoft.Xna.Framework.Graphics;
 using Microsoft.Xna.Framework.Input;
 using Microsoft.Xna.Framework.Storage;
 
 namespace FireEffect
 {
     public class Game1 : Game
     {
         GraphicsDeviceManager graphics;
         SpriteBatch spriteBatch;
 
         private const int _WIDTH = 300;   // 'heat' array size 
         private const int _HEIGHT = 350; // 
 
         private const int _FIREBASELENGTH = 5; // base line length 
         private const int _MAXCOLORS = 255; // Maximum palette colors
 
         private byte[] Back = new byte[_WIDTH*_HEIGHT];
         private byte[] Fore = new byte[_WIDTH*_HEIGHT];
         private Int32 [] w = new Int32[_WIDTH*_HEIGHT];
 
         private static Random r = new Random (); // Working random object
         private static System.Drawing.Color[] Palette = new System.Drawing.Color[_MAXCOLORS];
 
         Texture2D texture = null;
 
         protected void Gradient (int ao, int af)
         {
             int d = af - ao; // total distance between colors
 
             float 
             ro = Palette [ao].R, // Initial r,g,b values
             go = Palette [ao].G,
             bo = Palette [ao].B,
             dr = (Palette[af].R - ro)/d, // diferential of r,g,b
             dg = (Palette[af].G - go)/d, 
             db = (Palette[af].B - bo)/d;
 
             // lets fill each color in palette between range
             for (int i=0;i<d+1;i++)
                 Palette [i + ao] = 
                     System.Drawing.Color.FromArgb (
                         (byte)(ro + i*dr), 
                         (byte)(go + i*dg), 
                         (byte)(bo + i*db)
                     );
         }
 
         protected void FillGradients(System.Drawing.Color[] C)
         {
             // counting the back color, 0 indexed, there will be C.Length + 1 color turns. 
             int m = _MAXCOLORS / (C.Length+1);
 
             // first color (last one, 0, 'coldest' color) will be back page color
             Palette[0] = System.Drawing.Color.Black;
 
             // Lets point each choosen color at position and do gradient between them
             for (int i = 1; i < C.Length+1; i++) 
             {
                 Palette [i * m] = C [i-1];
                 Gradient ((i - 1) * m, i * m);
             }
         }
 
         public void btRandomPalette_Click (object sender, EventArgs args)
         {
             System.Drawing.Color[] C = new System.Drawing.Color[6];
 
             for (int i = 0; i < C.Length; i++) 
                 C[i] = System.Drawing.Color.FromArgb (r.Next () % 0xff, r.Next () % 0xff, r.Next () % 0xff);
 
             FillGradients (C);
         }
 
         private void SwapRB()
         {
             System.Drawing.Color c;
 
             for (int i = 0; i < Palette.Length; i++) 
             {
                 c = Palette [i];
                 Palette [i] = System.Drawing.Color.FromArgb (c.B, c.G, c.R);
             }
         }
 
         public void btDefaultPalette_Click (object sender, EventArgs args)
         {
             System.Drawing.Color[] C = new System.Drawing.Color[6];
 
             C [0] = System.Drawing.Color.Navy;
             C [1] = System.Drawing.Color.OrangeRed;
             C [2] = System.Drawing.Color.Orange;
             C [3] = System.Drawing.Color.Yellow;
             C [4] = System.Drawing.Color.White;
             C [5] = System.Drawing.Color.White;
 
             FillGradients (C);
             SwapRB ();
         }
 
         protected void Burn()
         {
             int x, y;
             int c;
 
             for (y = 1; y < _HEIGHT - 1; y++) 
                 for (x = 1; x < _WIDTH - 1; x++) 
                 {
                     // Get all surrounding color indexs and do mean...
                     c = (Back [x - 1 + (y - 1)*_WIDTH] + Back [x + (y - 1)*_WIDTH] + Back [x + 1 + (y - 1)*_WIDTH] +
                         Back [x - 1 + y*_WIDTH]  +  /*   (x,y)    */     Back [x + 1 + y*_WIDTH]     + 
                         Back [x - 1 + (y + 1)*_WIDTH] + Back [x + (y + 1)*_WIDTH] + Back [x + 1 + (y + 1)*_WIDTH])
                         >> 3;
 
                     // ...and we put it in the upper pixel. And then, fire grows...
                     Fore [x + (y - 1)*_WIDTH] = (byte)c;
                 }
         }
 
         protected void FireBase()
         {
             byte c = (byte)(_MAXCOLORS-1);
             int x, y;
 
             // Lets do, at image base, some random color lines
             for (y = _HEIGHT - 3; y < _HEIGHT; y++)
                 for (x = 0; x < _WIDTH; x ++) 
                 {
                     if (x % _FIREBASELENGTH == 0)
                         c = (byte)(Back [x + y*_WIDTH] + r.Next () % _MAXCOLORS);
 
                     Back [x + y*_WIDTH] = (byte)(c % _MAXCOLORS - 1);
                 }
         }
 
         protected void DumpToBitmap()
         {
             int x, y;
             System.Drawing.Color c;
 
             for (x = 0; x < _WIDTH; x++)
                 for (y = 0; y < _HEIGHT; y++) 
                 {
                     c = Palette [Fore [x + y * _WIDTH]];
                     w [x + y * _WIDTH] = c.ToArgb();
                 }
 
             texture.SetData<Int32> (w);
 
             Array.Copy(Fore,Back,_WIDTH * _HEIGHT);
         }
 
         public Game1 ()
         {
             graphics = new GraphicsDeviceManager (this);
 
             graphics.PreferredBackBufferWidth = _WIDTH;
             graphics.PreferredBackBufferHeight = _HEIGHT;
             graphics.IsFullScreen = false;
             graphics.ApplyChanges ();
 
             Content.RootDirectory = "Content";                        
         }
             
         protected override void Initialize ()
         {
             base.Initialize ();
             texture = new Texture2D (graphics.GraphicsDevice, _WIDTH, _HEIGHT);
 
             btDefaultPalette_Click (null, null);
         }
             
         protected override void LoadContent ()
         {
             spriteBatch = new SpriteBatch (GraphicsDevice);
         }
             
         protected override void Update (GameTime gameTime)
         {
             if (Keyboard.GetState ().IsKeyDown (Keys.Escape)) {
                 Exit ();
             }
                 
             if (Keyboard.GetState().IsKeyDown(Keys.Space))
             {
                 graphics.PreferredBackBufferHeight = _HEIGHT;
                 graphics.PreferredBackBufferWidth = _WIDTH;
                 graphics.ApplyChanges();
             }
 
             if (Keyboard.GetState ().IsKeyDown (Keys.R))
                 btRandomPalette_Click (null, null);
 
             if (Keyboard.GetState ().IsKeyDown (Keys.D))
                 btDefaultPalette_Click (null, null);
 
             base.Update (gameTime);
 
             Burn ();
             DumpToBitmap ();
             FireBase ();
         }
             
         /// <param name="gameTime">Provides a snapshot of timing values.</param>
         protected override void Draw (GameTime gameTime)
         {
             base.Draw (gameTime);
 
             spriteBatch.Begin ();
             spriteBatch.Draw(texture, Vector2.Zero, Microsoft.Xna.Framework.Color.White);
             spriteBatch.End ();
         }
     }
 }

Hope you like it!

Best,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s