The Dancing Pixels, or the Pixels in Water

Environment: Visual C++

To start with, the program is a small-time animation in VC++. What does an image look like when it is dipped in a tub of water when the tub is subjected to a constant vibration? The more difficult part is to produce the ripple effect, as when a pebble is thrown in the tub. So, I will tell at least this much.

The difference I make is that I am going to explain how to produce this effect, not simply copy and paste code form somewhere. Much of the code that I saw over the Net regarding small-time animation carries no explanation at all.

Some Theory

Let p(x,y) be a pixel at the x,y coordinate in an image. Imagine the pixel p(x,y) moving randomly around its four m-neighbourhoods, namely p(x-m,y-m),p(x+m,y+m), p(x-m,y),p(x,y-m) randomly, where m is a natural number {1,2,3,4,5,6,…}. Think what will happen if all the pixels in the image move in such a manner; you get the effect.

The Practice

When someone clicks the Ok button, I do the following:

CDC me1;
CBitmap ma1;
BITMAP info1;

CDC me3;
CBitmap ma3;
BITMAP info3;

All the above all globals.

ma1.LoadBitmap(IDB_BITMAP1);
me1.CreateCompatibleDC(dc);
ma1.GetBitmap(&info1);
me1.SelectObject(ma1);

me3.CreateCompatibleDC(dc);
ma3.CreateCompatibleBitmap(dc,1000,1000);
me3.SelectObject(ma3);
me3.BitBlt(0,0,1000,1000,dc,0,0,SRCCOPY);

I have one more copy of the DC just in case you need it for more animation tricks; it is not required here, though.

me3.BitBlt(0,0,info1.bmWidth,info1.bmHeight,&me1,0,0,SRCCOPY);


HBITMAP hbm=NULL;

HDC DeskHdc=::GetDC(NULL);
HDC hdc=::CreateCompatibleDC(DeskHdc);

BitmapInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
BitmapInfo.bmiHeader.biWidth=info1.bmWidth;
BitmapInfo.bmiHeader.biHeight=info1.bmHeight;
BitmapInfo.bmiHeader.biSizeImage=(((info1.bmWidth * 32 + 31)
                                    & ~31) >> 3) * info1.bmHeight;
BitmapInfo.bmiHeader.biCompression=BI_RGB ;
BitmapInfo.bmiHeader.biXPelsPerMeter=0;
BitmapInfo.bmiHeader.biYPelsPerMeter =0;
BitmapInfo.bmiHeader.biClrImportant = 0;
BitmapInfo.bmiHeader.biClrUsed  = 0;    // we are not using palette
BitmapInfo.bmiHeader.biPlanes   = 1;    // has to be 1
BitmapInfo.bmiHeader.biBitCount = 32;   // as we want true-color


hbm=::CreateDIBSection(GetDesktopWindow()->GetWindowDC()
                                         ->m_hDC,
      &BitmapInfo,DIB_RGB_COLORS,(void**)&imageData,NULL,0);
HBITMAP m_hbmOld;

if (hbm)
  {
  m_hbmOld = (HBITMAP)::SelectObject(hdc,hbm);
}

::BitBlt(hdc,0,0,info1.bmWidth,info1.bmHeight,me3.m_hDC,0,0,
         SRCCOPY);

The above is a standard piece of code that would be given in any good VC++ books.

It is done so that I get access to the image’s bits that are now in the imageData member; the trick is to manipulate the bits directly and use SetDIBitsToDevice(–) to plot the image quickly and finally to repeat the process in a timer.

So, let us look at the OnTimer function: When we enter the timer for the first time, I want to copy the image bits array into another pointer, namely p, and a two-dimensional array, namely imgArr.

if(r= =0)
{
  memcpy(p,imageData,BitmapInfo.bmiHeader.biSizeImage);
  for(intj=0;j<=info1.bmHeight;j++)
  for(intk=0;k<=info1.bmWidth;k++)
  {
  imgArr[j][k]=p[j*info1.bmWidth+k]; // See note below for this
                                     // calculation
  }
  r= 1;
}

I want to stop this timer until I finish the following processing:

KillTimer(0);

Run through the whole image:

for(intk=0;k<=info1.bmHeight;k++)
for(intf=0;f<=info1.bmWidth;f++)
{
int R,G,B;
int Rx,Gx;

//Rx=rand()%(int)m+k;
//Gx=rand()%(int)m+f;

Then, I pick up a random 3-neighbouring pixel from the place (k,f):

Rx=rand()%(int)3+k;
Gx=rand()%(int)3+f;

Put the current pixel (the k,f guy) = the so-selected 3-neighbouring pixel:

p[k*info1.bmWidth+f]=imgArr[Rx][Gx];    // For more explanation
                                        // of this calculation,
                                        // see the note at the end
}

Now, all the pixels in the image have been replaced by their 3-neighbouring guys, that too randomly:

m-=.5;
if(m<1)
  m=20;
if(r>info1.bmWidth)
  r=0;
SetDIBitsToDevice(::GetDC(NULL),
    0,
    0,
    info1.bmWidth,
    info1.bmHeight,
    0,
    0,
    0,
    info1.bmHeight,
    p,
    //imageData,
    &BitmapInfo,
    DIB_RGB_COLORS);

Now, resume the timer for the next round of random replacement:

SetTimer(0,30,0);

Note: One item worth mentioning will be how the bits are stored in one single array. From p[0] to p[info1.bmWidth], we have a single row of the image, then from p[info1.bmWidth+1] to p[info1.bmWidth+ info1.bmWidth] is the second row of the image and so on until p[info1.bmWidth*info1.bmHeight].

Downloads


Download source – 462 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read