📜 ⬆️ ⬇️

A bit about creating demos, part 1

Hello!
This article is primarily for beginners, those who only decided to take up the demoscene, if the article is positively accepted by the community, I will make a cycle of several articles on the creation of demos. Each article will be short, but at the end of each article will be quite a working example.
I'll warn you right away that this article is not about how to do Demo using OpenGL, DirectX, and millions of shaders. There are a lot of good articles about this, I will write about drawing in memory.


What to write?


First you need to figure out how and on what we will write a demo.
We will write in C using Visual Studio 2008.

Several organizational moments


Let's first describe the TImage structure in the Images.h module; it will store information about the image:
struct TImage { int width; int height; unsigned char *pBitMap; }; 

')
Images.h, Images.c
 #pragma once struct TImage { int width; int height; unsigned char *pBitMap; }; void imgClearRGBA(struct TImage Image, unsigned char R, unsigned char G, unsigned char B, unsigned char A ); void imgClear(struct TImage Image, unsigned long color ); 

 /* Images */ #include "Images.h" void imgClearRGBA(struct TImage Image, unsigned char R, unsigned char G, unsigned char B, unsigned char A ) { int i; for(i=0;i!=Image.width*Image.height*4;i=i+4) { Image.pBitMap[ i ] = B; Image.pBitMap[ i+1 ] = G; Image.pBitMap[ i+2 ] = R; Image.pBitMap[ i+3 ] = A; } } void imgClear(struct TImage Image, unsigned long color ) { unsigned long *pBitMap; int i; pBitMap = (unsigned long*)Image.pBitMap; for(i=0;i!=Image.width*Image.height;i++) { pBitMap[ i ] = color; } } 



And the image bitmap itself will be stored in the array to which pBitMap will point.

Since we will not use OpenGL and DirectX, we must decide how we will display the pixels on the screen.
The variant with SetPixel () does not suit us because of its slowness.
The WinApi StretchDIBits () function comes to the rescue, it displays an array of pixels on the Handle, scaling in parallel, if necessary.
Here is the function that displays an array, where each pixel consists of 4 bytes, on the screen:

 void DrawBuffer(struct TImage Image) { BITMAPINFO BitMapInfo; DC=GetDC(Wnd); BitMapInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); BitMapInfo.bmiHeader.biWidth=Image.width; BitMapInfo.bmiHeader.biHeight=Image.height; BitMapInfo.bmiHeader.biPlanes=1; BitMapInfo.bmiHeader.biBitCount=32; BitMapInfo.bmiHeader.biCompression=BI_RGB; StretchDIBits( DC, 0, 0, Image.width*PIXEL_SIZE, Image.height*PIXEL_SIZE, 0, 0, Image.width, Image.height, Image.pBitMap, &BitMapInfo, DIB_RGB_COLORS, SRCCOPY ); ReleaseDC(Wnd, DC); } 


SystemUtils.h, SystemUtils.c
 #pragma once #define PIXEL_SIZE 1 #define DISP_WIDTH 640 #define DISP_HEIGHT 480 void DrawBuffer(struct TImage Image); #include "windows.h" void SetHWND( HWND _Wnd ); 

 /* SystemUtils */ #include "windows.h" #include "Images.h" #include "SystemUtils.h" static HWND Wnd; void SetHWND( HWND _Wnd ) { Wnd = _Wnd; } void DrawBuffer(struct TImage Image) { BITMAPINFO BitMapInfo; HDC DC; DC = GetDC(Wnd); BitMapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);//   BitMapInfo.bmiHeader.biWidth = Image.width;//   BitMapInfo.bmiHeader.biHeight = -Image.height;//  ,        BitMapInfo.bmiHeader.biPlanes = 1;//   -  1 BitMapInfo.bmiHeader.biBitCount = 32;// -    BitMapInfo.bmiHeader.biCompression = BI_RGB;//  BitMapInfo.bmiHeader.biSizeImage = Image.width*Image.height*32/8;//   StretchDIBits( DC, 0, 0, Image.width*PIXEL_SIZE, Image.height*PIXEL_SIZE, //    0, 0, Image.width, Image.height, //    Image.pBitMap, //     &BitMapInfo, //  DIB_RGB_COLORS, //   SRCCOPY ); //   ReleaseDC(Wnd, DC); } 



The classic loop looks like this:
 while(1) { ;   ; Pause(); } 

But we work in Windows, therefore we constantly have to process window messages, otherwise the user will get the impression that the program is frozen, because in any case we will call Pause (), why not process window messages in it?
No, of course, you can use the State Machine, but who needs a giant and clumsy State Machine in the demo (I'm not talking about games).
Look like pause.c will be like this:

Pause.h, Pause.c
 #pragma once #include "windows.h" void SetMsg( MSG _Msg ); void SetPause( DWORD value ); void Pause(void); 

 /*       */ //  timeGetTime   GetTickCount #define _USE_TIMEGETTIME #include "windows.h" #ifdef _USE_TIMEGETTIME #include "mmsystem.h" #pragma comment (lib,"winmm") #endif static MSG Msg; DWORD Time; DWORD OldTime; void SetMsg( MSG _Msg ) { Msg = _Msg; } void SetPause( DWORD value ) { if(value == 0)value = 1; Time = value; #ifdef _USE_TIMEGETTIME timeBeginPeriod(1);//    timeGetTime OldTime = timeGetTime()+Time; #else OldTime = GetTickCount()+Time; #endif } void Pause(void) { //   while (PeekMessage(&Msg, 0, 0, 0, PM_NOREMOVE) != 0 ) { if (GetMessage(&Msg, 0, 0, 0) ) { TranslateMessage(&Msg); DispatchMessage(&Msg); } } #ifdef _USE_TIMEGETTIME while( timeGetTime()<OldTime)Sleep(1);//       OldTime = timeGetTime()+Time; #else while( GetTickCount()<OldTime)Sleep(1);//       OldTime = GetTickCount()+Time; #endif } 



It is not very beautiful, but it works quite reliably.
Lovers of multithreading can simply make a separate thread.

It remains to create a window:
Main.c
 /*           */ #include "windows.h" #include "windowsx.h" #include "Demo.h" #include "SystemUtils.h" #include "Pause.h" static HWND Wnd; static MSG Msg; static WNDCLASS wndclass; /*    , ..     Windows          "".  -    */ static POINT MouseInWin; static RECT WinRect; static int MoveWin = 0; // window procedure LONG WINAPI WndProc ( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LONG lRet = 1; POINT point; switch (uMsg) { case WM_LBUTTONDOWN: MoveWin = 1;//move window=true GetWindowRect( Wnd, &WinRect ); point.x = GET_X_LPARAM(lParam); point.y = GET_Y_LPARAM(lParam); ClientToScreen( Wnd, (LPPOINT)&point ); MouseInWin.x = point.x-WinRect.left; MouseInWin.y = point.y-WinRect.top; SetCapture(Wnd); break; case WM_MOUSEMOVE: GetCursorPos( (LPPOINT)&point ); if(MoveWin)SetWindowPos( Wnd,0, point.x-MouseInWin.x, point.y-MouseInWin.y, WinRect.right-WinRect.left, WinRect.bottom-WinRect.top, 0); break; case WM_LBUTTONUP: MoveWin = 0;//move window=false ReleaseCapture(); break; case WM_RBUTTONUP: PostMessage(Wnd, WM_DESTROY, 0, 0); break; case WM_DESTROY: PostQuitMessage (0); ExitProcess( 0 ); break; default: lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); break; } return lRet; } void CreateWin( HINSTANCE hInstance, HWND *Wnd) { const int ClientWidth = DISP_WIDTH*PIXEL_SIZE;//resolution * pixel size const int ClientHeight = DISP_HEIGHT*PIXEL_SIZE; RECT Rect = {0,0,ClientWidth,ClientHeight}; wndclass.style = CS_BYTEALIGNCLIENT; wndclass.lpfnWndProc = &WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = 0; wndclass.hIcon = LoadIcon(0, L"idi_Application"); wndclass.hCursor = LoadCursor (0,IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); wndclass.lpszMenuName = L""; wndclass.lpszClassName = L"MainWindow"; RegisterClass(&wndclass); *Wnd=CreateWindow( L"MainWindow", L"Demo", WS_POPUPWINDOW, // CW_USEDEFAULT,//x CW_USEDEFAULT,//y ClientWidth,//width ClientHeight,//height 0,// parent win 0,//menu hInstance, 0//other ); GetWindowRect(*Wnd,&Rect); Rect.bottom = Rect.left+ClientHeight;//ClientHeight Rect.right = Rect.top+ClientWidth;//ClientWidth AdjustWindowRect(&Rect, GetWindowLong(*Wnd,GWL_STYLE) ,0); SetWindowPos(*Wnd,0,Rect.left,Rect.top, Rect.right-Rect.left, Rect.bottom-Rect.top,0); ShowWindow(*Wnd, SW_SHOW ); UpdateWindow (*Wnd); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { CreateWin(hInstance, &Wnd);//  SetHWND(Wnd); SetMsg(Msg); StartDemo(); return 0; } 


And create a module Demo.c, where the demo itself will be:
Demo.h, Demo.c
 void StartDemo(void); 

 #include "Pause.h" #include "SystemUtils.h" #include "Images.h" #include "math.h" static unsigned char BitMap[640*480*4];//   static struct TImage Disp = {640,480,BitMap};// void StartDemo(void) { SetPause(16);//fps ~= 60 while(1) { // AARRGGBB imgClear(Disp,0xFF000000);//  DrawBuffer(Disp);//   Pause(); } } 


Now you can run!

But for now, except for an empty window, we will not see anything, that's right, because in the cycle we only clear the buffer and copy it to the screen:
 void StartDemo(void) { SetPause(16);//fps ~= 60 while(1) { // AARRGGBB imgClear(Disp,0xFF000000);//  DrawBuffer(Disp);//   Pause(); } } 


Draw a point



Let's first draw a red point with coordinates x = 1, y = 1.
The screen for us will look like this:

Where each pixel of the image can be represented in the form of 4 unsigned char or one unsigned long:


The fact that gray is alpha, we will not touch it for now.

Now we need to calculate by the formula (x + y * disp.width) << 2 the position of the point in the array and add to it the offset for the red color, in our case it is equal to the 2nd. (<< 2 is just a quick multiplication by 4 shift)
 void StartDemo(void) { const int x=1, y=1; SetPause(16);//fps ~= 60 while(1) { // AARRGGBB imgClear(Disp,0xFF000000);//  Disp.pBitMap[( (x + y*Disp.width)<<2 )+2] = 255;//   DrawBuffer(Disp);//   Pause(); } } 

Now when you start you really see the red dot.

Draw gradient



Now let's draw a gradient.
To do this, we walk through all the pixels in the array using two nested loops,
and using the formula Value = X / (Max_X / Max_Value), draw a gradient, and for the red and blue channel its own, which will give a beautiful picture.
 void StartDemo(void) { int x,y,line; SetPause(16);//fps ~= 60 imgClear(Disp,0xFF000000);//  while(1) { for(y=0;y!=Disp.height;y++) { line = y*Disp.width; for(x=0;x!=Disp.width;x++) { Disp.pBitMap[( (x + line)<<2 )+0] = (unsigned char)( y/(Disp.height/256.0) );//B Disp.pBitMap[( (x + line)<<2 )+2] = (unsigned char)( x/(Disp.width/256.0) );//R } } DrawBuffer(Disp);//   Pause(); } } 

It immediately becomes obvious that this code can be highly optimized; you can do this if you want.

Simplest plasma



Let's draw the simplest plasma, however, until I explain the principle of its work, this is the topic of the next article. I will only note that instead of the sin () function, a sine table is used here, which increases the speed several times.
Demo.c
 #include "Pause.h" #include "SystemUtils.h" #include "Images.h" #include "math.h" static unsigned char BitMap[640*480*4];//   static struct TImage Disp = {640,480,BitMap};// static unsigned char SinT[256];//  static void CreatetSinT(void) { int i; for(i=0;i!=256;i++) { SinT[i] = (int)( sin( (i*3.14*2.0)/256.0) *128+128 ); } } void StartDemo(void) { int x,y,line; int tx1=0,ty1=0,tx2=0,ty2=0,tx3=0,ty3=0; int px1,py1,px2,py2,px3,py3; SetPause(16);//fps ~= 60 CreatetSinT(); imgClear(Disp,0xFF000000);//  while(1) { py1=ty1; py2=ty2; py3=ty3; for(y=0;y!=Disp.height;y++) { line = y*Disp.width; px1=tx1; px2=tx2; px3=tx3; for(x=0;x!=Disp.width;x++) { px1=px1+1; px2=px2+1; px3=px3+1; Disp.pBitMap[( (x + line)<<2 )+2] = (SinT[ px1&255 ]+SinT[ py1&255 ])>>1;//R Disp.pBitMap[( (x + line)<<2 )+1] = (SinT[ px2&255 ]+SinT[ py2&255 ])>>1;//G Disp.pBitMap[( (x + line)<<2 )+0] = (SinT[ px3&255 ]+SinT[ (py3+63)&255 ])>>1;//B } py1=py1+1; py2=py2+1; py3=py3+1; } tx1=tx1+1; ty1=ty1+1; tx2=tx2+2; ty2=ty2+2; tx3=ty3+3; ty3=ty3+3; DrawBuffer(Disp);//   Pause(); } } 


Download the source:
Empty window
Point
Gradient
Plasma
In the next article we will talk about drawing plasma.
I hope you were interested.
Thanks for attention!

Source: https://habr.com/ru/post/165279/


All Articles