Snippets

Milfeulle C++: GDI Animation

Created by Milfeulle
#include <windows.h>
#include <iostream>
#include <cmath>
#include <algorithm>

using namespace std;

struct pixel {
   unsigned char b;
   unsigned char g;
   unsigned char r;
   unsigned char a;
};

// Image size
const int Buffer_Width  = 250;
const int Buffer_Height = 175;

// Main_Window client size
const int Viewport_Width  = 1000;
const int Viewport_Height = 700;

/* 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.
 */
int Speed = 100;
const int fps = 20;

unsigned int Keyboard_State = 0;
#define KBD_UP     1
#define KBD_DOWN   2
#define KBD_ESCAPE 4

// Global Windows/Drawing variables
HBITMAP Offscreen_BM;
HWND Main_Window;

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

void Calculate_Frame (pixel *pixels, float Delta) {
   // This is where all the drawing takes place
   pixel *p;

   static float Frame_Offset = 0;

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

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

         px = float (x) / float (Buffer_Width);
         py = float (y) / float (Buffer_Height);

         p->r = 127 + (unsigned char)(1270.0 * sin (3.0 * Frame_Offset) *
           (cos (px + 10.0 * Frame_Offset) / sin (py + Frame_Offset)));
         p->g = ~p->r;
         p->b = -1;
         p->a = 0;
      }
   }
   Frame_Offset += Delta;
}

void MakeSurface (HWND Main_Window) {
   /* 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 = Buffer_Width;
   bmi.bmiHeader.biHeight =  -Buffer_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;

   // Create DIB section to always give direct access to pixels
   HDC Main_Window_DC = GetDC (Main_Window);
   Offscreen_BM = CreateDIBSection
      (Main_Window_DC, &bmi, DIB_RGB_COLORS, (void**)&pixels, NULL, 0);
   DeleteDC (Main_Window_DC);

   // Notice about priority
   if (!SetThreadPriority
       (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL))
   {
      printf ("Failed to set realtime mode!\n");
   }
}

LRESULT CALLBACK WndProc
  (HWND Main_Window,
   UINT Message,
   WPARAM wParam,
   LPARAM lParam)
{
   switch (Message) {
      case WM_CREATE:
         MakeSurface (Main_Window);
         break;
      case WM_CLOSE:
         DestroyWindow (Main_Window);
         break;
      case WM_DESTROY:
         PostQuitMessage (0);
         break;
      case WM_ERASEBKGND:
         return LRESULT (TRUE);
      case WM_PAINT:
         {
            // Do nothing here
            PAINTSTRUCT ps;
            BeginPaint (Main_Window, &ps);
            EndPaint (Main_Window, &ps);
         }
         break;
      case WM_KEYDOWN:
         {
            switch (wParam) {
               case VK_UP:
                  Keyboard_State |= KBD_UP;
                  break;
               case VK_DOWN:
                  Keyboard_State |= KBD_DOWN;
                  break;
               case VK_ESCAPE:
                  Keyboard_State |= KBD_ESCAPE;
                  break;
               default:
                  return DefWindowProc (Main_Window, Message, wParam, lParam);
            }
         }
         break;
      default:
         return DefWindowProc (Main_Window, Message, wParam, lParam);
   }
   return LRESULT (FALSE);
}

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

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

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

   // Make window
   Main_Window = CreateWindowEx
     (WS_EX_APPWINDOW,
      "Animation_Class",
      "Animation",
      WS_MINIMIZEBOX | WS_SYSMENU | WS_POPUP | WS_CAPTION,
      10, 10, Viewport_Width, Viewport_Height,
      NULL, NULL, hInstance, NULL );

   RECT rcClient, rcWindow;
   POINT ptDiff;

   // Get window and client sizes
   GetClientRect (Main_Window, &rcClient);
   GetWindowRect (Main_Window, &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
     (Main_Window,
      rcWindow.left,
      rcWindow.top,
      Viewport_Width + ptDiff.x,
      Viewport_Height + ptDiff.y,
      FALSE);

   ShowWindow   (Main_Window, SW_SHOW);
   UpdateWindow (Main_Window);

   // Milliseconds to wait each frame
   unsigned long delay = 1000 / fps;

   // Create an unnamed waitable synchronization timer.
   LARGE_INTEGER liDueTime;
   liDueTime.QuadPart = LONGLONG (delay);
   HANDLE Frame_Rate_Timer = CreateWaitableTimer (NULL, FALSE, NULL);
   SetWaitableTimer (Frame_Rate_Timer, &liDueTime, delay, NULL, NULL, FALSE);

   // Retrieve the window's DC
   HDC Main_Window_DC = GetDC (Main_Window);
   HDC Offscreen_DC = CreateCompatibleDC (Main_Window_DC);
   HBITMAP Offscreen_BM_Old = HBITMAP
     (SelectObject (Offscreen_DC, Offscreen_BM));
   // Set transparent background
   SetBkMode (Offscreen_DC, TRANSPARENT);
   // Disable batching
   GdiSetBatchLimit (1);

   // Init time counters
   LARGE_INTEGER Time;
   LARGE_INTEGER Frequency;
   LONGLONG Previous_Time = 0;
   LONGLONG Current_Time  = 0;
   int Used_Time    = 0;
   int Elapsed_Time = 0;

   RECT FPS_Text_Rect;
   FPS_Text_Rect.left = 2;
   FPS_Text_Rect.top  = 2;
   FPS_Text_Rect.right  = 102;
   FPS_Text_Rect.bottom = 22;
   char FPS_Text_String [50];
   int FPS_Text_String_End;

   QueryPerformanceFrequency (&Frequency);

   // Init super-cycle properties
   BOOL  Is_Quit_Message = FALSE;
   DWORD Wait_Result     = WAIT_OBJECT_0;

   // Start super-cycle
   while (!Is_Quit_Message) {
      Previous_Time = Current_Time;
      QueryPerformanceCounter (&Time);
      Current_Time = Time.QuadPart;
      Elapsed_Time = int
        ((1000 * (Current_Time - Previous_Time)) / Frequency.QuadPart);

      // Draw pixels to window
      StretchBlt
         (Main_Window_DC, 0, 0, Viewport_Width, Viewport_Height,
         Offscreen_DC, 0, 0, Buffer_Width, Buffer_Height,
         SRCCOPY);

      // Process main window messages
      while (PeekMessage (&Message, NULL, 0, 0, PM_REMOVE) > 0) {
         Is_Quit_Message = (Message.message == WM_QUIT);
         TranslateMessage (&Message);
         DispatchMessage (&Message);
      }

      // Process keys
      if (Keyboard_State > 0) {
         if (Keyboard_State & KBD_UP) {
            Keyboard_State &= ~KBD_UP;
            Speed = max (Speed - Speed / 10, 10);
         }
         if (Keyboard_State & KBD_DOWN) {
            Keyboard_State &= ~KBD_DOWN;
            Speed = min (Speed + Speed / 10, 1000);
         }
         if (Keyboard_State & KBD_ESCAPE) {
            Keyboard_State &= ~KBD_ESCAPE;
            PostMessage (Main_Window, WM_CLOSE, 0, 0);
         }
      }

      // Do stuff with pixels
      Calculate_Frame (pixels, 10.0 / (float (fps * Speed)));

      // Make information text
      FPS_Text_String_End = sprintf
         (FPS_Text_String,
         "%2d / %2d ms\nSpeed: %3d",
         Used_Time, Elapsed_Time, 10000 / Speed);
      DrawText
         (Offscreen_DC,
         FPS_Text_String, FPS_Text_String_End,
         &FPS_Text_Rect,
         DT_LEFT | DT_NOCLIP);

      QueryPerformanceCounter (&Time);
      Used_Time = int
        ((1000 * (Time.QuadPart - Current_Time)) / Frequency.QuadPart);

      // Wait for timer
      Wait_Result = WaitForSingleObject (Frame_Rate_Timer, INFINITE);

      Is_Quit_Message = Is_Quit_Message || (Wait_Result != WAIT_OBJECT_0);
   }

   CloseHandle (Frame_Rate_Timer);

   SelectObject (Offscreen_DC, Offscreen_BM_Old);

   DeleteDC (Main_Window_DC);
   DeleteDC (Offscreen_DC);

   DeleteObject (Offscreen_BM);

   return 0;
}
-------------------------------------------------------------------------------
--  vim: set enc=cp1251 ts=3 sw=3 filetype=ada nobackup:                     --
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--  "THE CAKE-WARE LICENSE" (Revision 41):                                   --
--                                                                           --
--      Milfeulle <mail@milfie.uu.me> wrote this file. As long as you        --
--  retain this notice you can do whatever you want with this stuff. If we   --
--  meet some day, and you think this stuff is worth it, you can buy me      --
--  a cake in return.                                                        --
--                                                                           --
--      Milfie.                                                              --
-------------------------------------------------------------------------------

project GDI_Animation is

   for Languages use ("C++");
   for Main use ("gdi_animation.cpp");
   for Source_Dirs use (".");
   for Exec_Dir use "exec";
   for Object_Dir use "obj";

   package Naming is
      for Spec_Suffix ("c++") use ".hh";
      for Body_Suffix ("c++") use ".cpp";
   end Naming;

   package Linker is
      for Default_Switches ("c++") use ("-static", "-mconsole", "-lgdi32", "-s", "-Wl,--gc-sections");
   end Linker;

   package Compiler is
      for Default_Switches ("c++") use ("-O2", "-march=i586", "-ffunction-sections", "-fdata-sections", "-Wall");
   end Compiler;

   package Binder is
      for Default_Switches ("c++") use ("-static");
   end Binder;

end GDI_Animation;

Comments (0)