Click here to Skip to main content
16,022,333 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I was trying to make a eye move with easyX.

The problem is my image(the eye) flickers, but when I hold down the window, it becomes normal.

This is the code:
#include <graphics.h>
#include <string>

int EYE_SPEED = 20;
int idx_current_anim = 0;
const int EYE_ANIM_NUM =5;
bool running = true;
IMAGE background;
IMAGE eye[EYE_ANIM_NUM];
ExMessage msg;
POINT eye_pos = { 320,301 };

#pragma comment(lib,"MSIMG32.LIB")

void loadeye()
{
	for (size_t i = 0; i < EYE_ANIM_NUM; i++)
	{
		std::wstring path = L"img/eye" + std::to_wstring(i+1) + L".png";
		loadimage(&eye[i], path.c_str(),58,59);
	}
}

inline void putimage_alpha(int x, int y, IMAGE* img)
{
	int w = img->getwidth();
	int h = img->getheight();


	AlphaBlend(GetImageHDC(NULL), x, y, w, h,
		GetImageHDC(img), 0, 0, w, h,{AC_SRC_OVER,0,255,AC_SRC_ALPHA});
}



int main()
{
	initgraph(1280, 720);

	loadimage(&background, _T("img/background.png"));
	loadeye();

	BeginBatchDraw();

	while (running)
	{
		DWORD start = GetTickCount();

		while (peekmessage(&msg))
		{
			if (msg.message == WM_KEYDOWN)
			{
				switch (msg.vkcode)
				{
				case VK_UP:
					eye_pos.y -= EYE_SPEED;
					break;
				case VK_DOWN:
					eye_pos.y += EYE_SPEED;
					break;
				case VK_LEFT:
					eye_pos.x -= EYE_SPEED;
					break;
				case VK_RIGHT:
					eye_pos.x += EYE_SPEED;
					break;
				}
			}
		}

		static int counter = 0;
		if (++counter % 60 == 0)
			idx_current_anim++;
		idx_current_anim = idx_current_anim % EYE_ANIM_NUM;

		cleardevice();
		putimage(0, 0, &background);
		putimage_alpha(eye_pos.x, eye_pos.y, &eye[idx_current_anim]);
		FlushBatchDraw();

		EndBatchDraw();
		DWORD end = GetTickCount();
		DWORD delta = end - start;
		if (delta < 1000 / 144)
		{
			Sleep(1000 / 144 - delta);
		}
	}
}


I don't how to include photos. But eye1.png~eye5.png is the object that is moving and background.png is the background.

What I have tried:

I adjust the function "putimage_alpha" to "putimage", it becomes normal but with black blocks. So I think the problem is the "putimage_alpha" function.
Posted
Comments
Rick York 5-Jul-24 14:49pm    
What does "holding down the window" mean?

You're clearing the device, then composing the image using alpha bending on the CPU, and only then flushing it.

Which is asking for flicker. Set the HDC to a back buffer, compose the image off screen, then clear the device and flush the composed image to the screen in a single call.

You need to set the data within a vertical blank. Which Windows should do for you. But it can't if the draw routine takes longer than a blank.

Also, check the specs for BeginBatchDraw / FlushBatchDraw. You begin once, then flush many times. Which might be how you are meant to use those functions. But it looks a bit suspicious to me. I'd expect that you begin a batch, set it up, flush it, then destoy that batch and start over.
 
Share this answer
 
As others have already pointed out, cleardevice() is unnecessary and can be omitted, as everything is always redrawn anyway. If it still flickers, you would need double buffers and prepare the complete image in memory first.

The cause of the black blocks is to be found in the EasyX function loadimage() and not in the function putimage_alpha().

Although the EasyX function loadimage() can in principle load a png with alpha channel and even rescale the image while retaining the alpha channel, the quality is not satisfactory. For comparison, I tested the function load_image_with_transparency() with GDI+ and found that the effort is considerably higher, but the result is much better.

Here is a comparison of the functions:
C++
// EasyX function for loading an image with transparency and scaling
IMAGE load_image_with_transparency1(const wchar_t* filename, int width, int height)
{
  IMAGE img;
  loadimage(&img, filename, width, height); // load image and scale it
  return img;
}

C++
// GDI+ function for loading an image with transparency and scaling
IMAGE load_image_with_transparency2(const wchar_t* filename, int width, int height) 
{
    GDIPlusBitmap gdiPlusBitmap(filename, width, height);    
    HBITMAP hBitmap = gdiPlusBitmap.getHBitmap();    
    // Create an EasyX IMAGE object and copy the HBITMAP into it
    IMAGE img;
    getimage(&img, 0, 0, width, height);
    HDC hdcDest = GetImageHDC(&img);
    HDC hdcMem = CreateCompatibleDC(hdcDest);
    SelectObject(hdcMem, hBitmap);
    BitBlt(hdcDest, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY);    
    
    // Release resources
    DeleteDC(hdcMem);

    return img;
}

Whereby the GDIPlusBitmap is packaged as an object to keep it convenient and readable:
C++
// RAII class for HBITMAP
#include <memory>
class GDIPlusBitmap {
public:
    GDIPlusBitmap(const wchar_t* filename, int width, int height) {
        bitmap.reset(new Bitmap(filename));
        scaledBitmap.reset(new Bitmap(width, height));
        graphics.reset(Graphics::FromImage(scaledBitmap.get()));
        graphics->SetInterpolationMode(InterpolationModeHighQualityBicubic);
        graphics->DrawImage(bitmap.get(), 0, 0, width, height);
        scaledBitmap->GetHBITMAP(Color(0, 0, 0, 0), &hBitmap);
    }

    ~GDIPlusBitmap() {
        if (hBitmap) {
            DeleteObject(hBitmap);
        }
    }

    HBITMAP getHBitmap() const {
        return hBitmap;
    }

private:
    std::unique_ptr<Bitmap> bitmap;
    std::unique_ptr<Bitmap> scaledBitmap;
    std::unique_ptr<Graphics> graphics;
    HBITMAP hBitmap = nullptr;
};


// edit1:
EasyX already uses double buffering when BeginBatchDraw() and EndBatchDraw() are used. Alternative implementations of the drawing functions may undermine the system, which could make the use of a separate buffer system useful. BeginBatchDraw() and EndBatchDraw() are sufficient and easy to use for many applications with EasyX. For more performance-intensive applications, however, more powerful graphics frameworks such as SFML or SDL are required. These offer extensive functions for game development and graphical applications, including advanced buffering techniques.

// edit2:
In the given code, both FlushBatchDraw() and EndBatchDraw() are called for each run, which does not seem to make sense. FlushBatchDraw() would be sufficient here.
C++
FlushBatchDraw();
// EndBatchDraw();
 
Share this answer
 
v3

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900