1,105,271 Community Members

Fast Animation with the Windows GDI

Member Avatar
(William Hemsworth)
Reputation Points: 1,339 [?]
Q&As Helped to Solve: 158 [?]
Skill Endorsements: 10 [?]
 
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;
}
Member Avatar
William Hemsworth
Posting Virtuoso
1,546 posts since Mar 2008
Reputation Points: 1,339 [?]
Q&As Helped to Solve: 158 [?]
Skill Endorsements: 10 [?]
 
0
 

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

Attachments Speed_Test.zip (67.56KB)
Member Avatar
William Hemsworth
Posting Virtuoso
1,546 posts since Mar 2008
Reputation Points: 1,339 [?]
Q&As Helped to Solve: 158 [?]
Skill Endorsements: 10 [?]
 
0
 

Another really nice effect using this method :icon_smile:

Attachments Rainbow_Drawer.zip (21.01KB) prev.png 84.52KB
Member Avatar
William Hemsworth
Posting Virtuoso
1,546 posts since Mar 2008
Reputation Points: 1,339 [?]
Q&As Helped to Solve: 158 [?]
Skill Endorsements: 10 [?]
 
0
 

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

Member Avatar
optimumgaint
Newbie Poster
1 post since Dec 2009
Reputation Points: 0 [?]
Q&As Helped to Solve: 0 [?]
Skill Endorsements: 0 [?]
 
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++

Member Avatar
William Hemsworth
Posting Virtuoso
1,546 posts since Mar 2008
Reputation Points: 1,339 [?]
Q&As Helped to Solve: 158 [?]
Skill Endorsements: 10 [?]
 
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.

Member Avatar
firstPerson
Industrious Poster
4,052 posts since Dec 2008
Reputation Points: 761 [?]
Q&As Helped to Solve: 634 [?]
Skill Endorsements: 24 [?]
 
1
 

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

Member Avatar
William Hemsworth
Posting Virtuoso
1,546 posts since Mar 2008
Reputation Points: 1,339 [?]
Q&As Helped to Solve: 158 [?]
Skill Endorsements: 10 [?]
 
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:

Member Avatar
Dave Sinkula
long time no c
4,852 posts since Apr 2004
Reputation Points: 2,398 [?]
Q&As Helped to Solve: 340 [?]
Skill Endorsements: 69 [?]
Team Colleague
 
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.

Member Avatar
William Hemsworth
Posting Virtuoso
1,546 posts since Mar 2008
Reputation Points: 1,339 [?]
Q&As Helped to Solve: 158 [?]
Skill Endorsements: 10 [?]
 
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 :)

Member Avatar
Dave Sinkula
long time no c
4,852 posts since Apr 2004
Reputation Points: 2,398 [?]
Q&As Helped to Solve: 340 [?]
Skill Endorsements: 69 [?]
Team Colleague
 
0
 

Does giving the anonymous structure a name fix that?

Yup. I just named it s , for example.

Member Avatar
Bill Dreschel
Newbie Poster
2 posts since Oct 2010
Reputation Points: 0 [?]
Q&As Helped to Solve: 0 [?]
Skill Endorsements: 0 [?]
 
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

Member Avatar
Bill Dreschel
Newbie Poster
2 posts since Oct 2010
Reputation Points: 0 [?]
Q&As Helped to Solve: 0 [?]
Skill Endorsements: 0 [?]
 
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!

You
Post:
Start New Discussion
View similar articles that have also been tagged: