1.11M Members

Fast Animation with the Windows GDI

 
1
 

Quite a while ago, I made this snippet. This code is basically the same, except that it adds animation.

This method of blitting is very fast assuming you don't use a surface that's too large. On a 500 x 500 surface, I managed 350fps using only 0-1% of the cpu. This snippet may appear much slower because of the amount of CPU it's applying to each pixel, but the blitting itself is very fast. Also, don't forget that an average game will only redraw parts of the window that need redrawing, this redraws the whole surface every time.

So, as long as you know what you're doing, Windows GDI isn't actually that slow :icon_lol:

Attached executable:

GDI Animation.zip


Preview Image: prev.png

edit: Running in Debug may reduce speed by a lot, set it as Release.

Attachments GDI_Animation.zip (32.34KB) prev.png 135.75KB
#include <windows.h>
#include <iostream>
#include <cmath>

struct pixel {
  union {
    struct {
      /* 'a' unused, used for 32-bit alignment,
       * could also be used to store pixel alpha
       */
      unsigned char b, g, r, a;
    };
    int val;
  };

  pixel() {
    val = 0;
  }
};

// Window client size
const int width = 375;
const int height = 375;

/* Target fps, though it's hard to achieve this fps
 * without extra timer functionality unless you have
 * a powerfull processor. Raising this value will
 * increase the speed, though it will use up more CPU.
 */
const int fps = 50;

// Global Windows/Drawing variables
HBITMAP hbmp;
HANDLE hTickThread;
HWND hwnd;
HDC hdcMem;

// Pointer to pixels (will automatically have space allocated by CreateDIBSection
pixel *pixels;


void onFrame(pixel *pixels) {
  // This is where all the drawing takes place

  pixel *p;

  // +0.005 each frame
  static float frameOffset = 0;

  float px; // % of the way across the bitmap
  float py; // % of the way down the bitmap

  for (int x = 0; x < width; ++x) {
    for (int y = 0; y < height; ++y) {
      p = &pixels[y * width + x];

      px = float(x) / float(width);
      py = float(y) / float(height);

      p->r = unsigned char(((cos(px + frameOffset * 10) / sin(py + frameOffset)) * cos(frameOffset * 3) * 10) * 127 + 127);
      p->g = ~p->r;
      p->b = 255;
    }
  }

  frameOffset += 0.005f;
}



DWORD WINAPI tickThreadProc(HANDLE handle) {
  // Give plenty of time for main thread to finish setting up
  Sleep( 50 );
  ShowWindow( hwnd, SW_SHOW );

  // Retrieve the window's DC
  HDC hdc = GetDC( hwnd );

  // Create DC with shared pixels to variable 'pixels'
  hdcMem = CreateCompatibleDC( hdc );
  HBITMAP hbmOld = (HBITMAP)SelectObject( hdcMem, hbmp );

  // Milliseconds to wait each frame
  int delay = 1000 / fps;

  for ( ;; ) {
    // Do stuff with pixels
    onFrame( pixels );

    // Draw pixels to window
    BitBlt( hdc, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY );

    // Wait
    Sleep( delay );
  }

  SelectObject( hdcMem, hbmOld );
  DeleteDC( hdc );
}



void MakeSurface(HWND hwnd) {
  /* Use CreateDIBSection to make a HBITMAP which can be quickly
   * blitted to a surface while giving 100% fast access to pixels
   * before blit.
   */

  // Desired bitmap properties
  BITMAPINFO bmi;
  bmi.bmiHeader.biSize = sizeof(BITMAPINFO);
  bmi.bmiHeader.biWidth = width;
  bmi.bmiHeader.biHeight =  -height; // Order pixels from top to bottom
  bmi.bmiHeader.biPlanes = 1;
  bmi.bmiHeader.biBitCount = 32; // last byte not used, 32 bit for alignment
  bmi.bmiHeader.biCompression = BI_RGB;
  bmi.bmiHeader.biSizeImage = 0;
  bmi.bmiHeader.biXPelsPerMeter = 0;
  bmi.bmiHeader.biYPelsPerMeter = 0;
  bmi.bmiHeader.biClrUsed = 0;
  bmi.bmiHeader.biClrImportant = 0;
  bmi.bmiColors[0].rgbBlue = 0;
  bmi.bmiColors[0].rgbGreen = 0;
  bmi.bmiColors[0].rgbRed = 0;
  bmi.bmiColors[0].rgbReserved = 0;

  HDC hdc = GetDC( hwnd );

  // Create DIB section to always give direct access to pixels
  hbmp = CreateDIBSection( hdc, &bmi, DIB_RGB_COLORS, (void**)&pixels, NULL, 0 );
  DeleteDC( hdc );

  // Create a new thread to use as a timer
  hTickThread = CreateThread( NULL, NULL, &tickThreadProc, NULL, NULL, NULL );
}



LRESULT CALLBACK WndProc(
      HWND hwnd,
      UINT msg,
      WPARAM wParam,
      LPARAM lParam)
{
  switch ( msg ) {
    case WM_CREATE:
      {
        MakeSurface( hwnd );
      }
      break;
    case WM_PAINT:
      {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint( hwnd, &ps );

        // Draw pixels to window when window needs repainting
        BitBlt( hdc, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY );

        EndPaint( hwnd, &ps );
      }
      break;
    case WM_CLOSE:
      {
        DestroyWindow( hwnd );
      }
      break;
    case WM_DESTROY:
      {
        TerminateThread( hTickThread, 0 );
        PostQuitMessage( 0 );
      }
      break;
    default:
      return DefWindowProc( hwnd, msg, wParam, lParam );
  }

  return 0;
}



int WINAPI WinMain(
      HINSTANCE hInstance,
      HINSTANCE hPrevInstance,
      LPSTR lpCmdLine,
      int nShowCmd)
{
  WNDCLASSEX wc;
  MSG msg;

  // Init wc
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.cbSize = sizeof( WNDCLASSEX );
  wc.hbrBackground = CreateSolidBrush( 0 );
  wc.hCursor = LoadCursor( NULL, IDC_ARROW );
  wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
  wc.hIconSm = LoadIcon( NULL, IDI_APPLICATION );
  wc.hInstance = hInstance;
  wc.lpfnWndProc = WndProc;
  wc.lpszClassName = "animation_class";
  wc.lpszMenuName = NULL;
  wc.style = 0;

  // Register wc
  if ( !RegisterClassEx(&wc) ) {
    MessageBox( NULL, "Failed to register window class.", "Error", MB_OK );
    return 0;
  }

  // Make window
  hwnd = CreateWindowEx(
    WS_EX_APPWINDOW,
    "animation_class",
    "Animation",
    WS_MINIMIZEBOX | WS_SYSMENU | WS_POPUP | WS_CAPTION,
    300, 200, width, height,
    NULL, NULL, hInstance, NULL );


  RECT rcClient, rcWindow;
  POINT ptDiff;

  // Get window and client sizes
  GetClientRect( hwnd, &rcClient );
  GetWindowRect( hwnd, &rcWindow );

  // Find offset between window size and client size
  ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
  ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;

  // Resize client
  MoveWindow( hwnd, rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, false);

  UpdateWindow( hwnd );

  while ( GetMessage(&msg, 0, 0, NULL) > 0 ) {
    TranslateMessage( &msg );
    DispatchMessage( &msg );
  }

  return 0;
}
 
0
 

Here's a speed test :) mine stays at about 480fps with only 2-3% CPU.

Attachments Speed_Test.zip (67.56KB)
 
0
 

Another really nice effect using this method :icon_smile:

Attachments Rainbow_Drawer.zip (21.01KB) prev.png 84.52KB
 
0
 

Got it working with objects and bitmaps, I think with some optimizations I could use this to make some neat games :)

 
0
 
p->r = unsigned char(((cos(px + frameOffset * 10) / sin(py + frameOffset)) * cos(frameOffset * 3) * 10) * 127 + 127);
      p->g = ~p->r;

was error in my DEV C++

 
0
 

Sorry, I should have tested it with multiple compilers, replace that line with:

p->r = (unsigned char)(((cos(px + frameOffset * 10) / sin(py + frameOffset)) * cos(frameOffset * 3) * 10) * 127 + 127);

and it should work.

 
1
 

Pretty cool. Looks something someone will see if they were high of drugs.

 
0
 

Pretty cool. Looks something someone will see if they were high of drugs.

Haha, what have you been smoking? I'd go crazy if anything turned out that intense.
Thanks alot for the feedback :icon_razz:

 
1
 

Issues when building with mingw:

main.cpp:12: error: ISO C++ prohibits anonymous structs
main.cpp: In function `void onFrame(pixel*)':
main.cpp:60: error: expected primary-expression before "unsigned"
main.cpp:60: error: expected `;' before "unsigned"
main.cpp: In function `void MakeSurface(HWND__*)':
main.cpp:134: warning: passing NULL used for non-pointer converting 2 of `void* CreateThread(_SECURITY_ATTRIBUTES*, DWORD, DWORD (*)(void*), void*, DWORD, DWORD*)'
main.cpp:134: warning: passing NULL used for non-pointer converting 5 of `void* CreateThread(_SECURITY_ATTRIBUTES*, DWORD, DWORD (*)(void*), void*, DWORD, DWORD*)'
main.cpp: In function `int WinMain(HINSTANCE__*, HINSTANCE__*, CHAR*, int)':
main.cpp:237: warning: passing NULL used for non-pointer converting 4 of `BOOL GetMessageA(tagMSG*, HWND__*, UINT, UINT)'

This includes the cast you've already corrected. So it's mostly just that anonymous struct you may want to look at.

[edit]Also, the necessary library to link with:

-LD:\Programs\CodeBlocks\MinGW\lib -lgdi32

Where the path to the library will be whatever your install might be.

 
0
 

This includes the cast you've already corrected. So it's mostly just that anonymous struct you may want to look at.

Does giving the anonymous structure a name fix that?
Never thought there would be so many compiler issues, and I didn't realize that anonymous structs weren't allowed.

Thanks :)

 
0
 

Does giving the anonymous structure a name fix that?

Yup. I just named it s , for example.

 
0
 

Hi, thanks for the cool code. I got it working in a Win32 app (project type choice) in VC++ Express 2010 by making the includes:
#include "stdafx.h"
#include "GDIWin32.h"
#include <windows.h>
#include <iostream>
#include <cmath>

and putting the L in front of the quoted text, as in:
// Init wc
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.cbSize = sizeof( WNDCLASSEX );
wc.hbrBackground = CreateSolidBrush( 0 );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wc.hIconSm = LoadIcon( NULL, IDI_APPLICATION );
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = L"animation_class";
wc.lpszMenuName = NULL;
wc.style = 0;

// Register wc
if ( !RegisterClassEx(&wc) ) {
MessageBox( NULL, L"Failed to register window class.", L"Error", MB_OK );
return 0;
}

Very cool, thanks very much. Any thoughts on how this could be implemented in a Windows Forms Application? (Sorry if this is a really stupid question, just starting in VC++.) Thanks again, Bill

 
2
 

Finally got around to putting this animation code into a Visual C++ 2010 Express Windows form application. To use it, start a new form build, add a PictureBox, name it "src" and make it some even dimension like 400,400. Add a button, name it "btnStart", text to "Start", add a timer to the form, set it to 20ms and disabled.
Add the attached code.

void DrawMath(void){
           // Create a new bitmap.
   Bitmap^ bmp = gcnew Bitmap(src->Size.Width,src->Size.Height);
   //bmp->PixelFormat=Format32bppArgb;
   // Lock the bitmap's bits.  
   Rectangle rect = Rectangle(0,0,src->Size.Width,src->Size.Height);
   System::Drawing::Imaging::BitmapData^ bmpData = bmp->LockBits( rect, System::Drawing::Imaging::ImageLockMode::ReadWrite, bmp->PixelFormat );

   // Get the address of the first line.
   IntPtr ptr = bmpData->Scan0;

   // Declare an array to hold the bytes of the bitmap.
   // This code is specific to a bitmap with 24 bits per pixels.
   int bytes = bmpData->Stride * bmp->Height;
   array<Byte>^rgbValues = gcnew array<Byte>(bytes);
   array<Byte>^rgbValues1;
   rgbValues1 = rgbValues;

   // Copy the RGB values into the array.

  // This is where all the drawing takes place


  // +0.005 each frame
  static double frameOffset = 0;

  double px; // % of the way across the bitmap
  double py; // % of the way down the bitmap
  double mycosoff;
  int width = src->Size.Width;
  int height = src->Size.Height;
  double frame10 = (10.0) * frameOffset;
  double cosoff = (1270) * cos(frameOffset * 3);
  double pyinc = (1.0) / double(height);
  int counter=0;
  for (int x = 0; x < width; ++x) {
    px = double(x) / double(width);
    mycosoff = cos(px + frame10) * cosoff;
    int ywidth=x;
    py=pyinc+frameOffset;
    for (int y = 0; y < height; ++y ) {
      unsigned char red = unsigned char(((mycosoff / sin(py))) + 127);
      rgbValues[counter++] = 0xff;
      rgbValues[counter++] = red;
      rgbValues[counter++] = ~red;
      rgbValues[counter++] = 0xff;
      ywidth+= width;
      py+=pyinc; 
    }
  }

  frameOffset += 0.005;

   // Copy the RGB values back to the bitmap
   System::Runtime::InteropServices::Marshal::Copy( rgbValues1, 0, ptr, bytes );

   // Unlock the bits.
   bmp->UnlockBits( bmpData );

   // Draw the modified image.
   //e->Graphics->DrawImage( bmp, 0, 150 );
        src->Image = bmp;
        src->Invalidate();
}
#pragma endregion
    private: System::Void timer1_Tick(System::Object^  sender, System::EventArgs^  e) {
                 DrawMath();
             }
    private: System::Void btnStart_Click(System::Object^  sender, System::EventArgs^  e) {
                 static int OnOff=0;
                 if (OnOff==0){
                     btnStart->Text = L"Stop";
                     timer1->Enabled = true;
                     OnOff=1;
                 }
                 else{
                     btnStart->Text = L"Start";
                     timer1->Enabled = false;
                     OnOff=0;
                 }

             }

Have fun! THANKS AGAIN FOR POSTING THE FAST ANIMATION SNIPPET!

Isn't it about time forums rewarded their contributors?

Earn rewards points for helping others. Gain kudos. Cash out. Get better answers yourself.

It's as simple as contributing editorial or replying to discussions labeled or OP Kudos

You
This is an OP Kudos discussion and contributors may be rewarded
Post:
Start New Discussion
View similar articles that have also been tagged: