/////////////////////////////////////
//
// 图像切换动画效果(像素点位移)
//
// Visual Studio 2022 | EasyX 20220610
//
// by huidong <mailhuid@163.com>
// 2022.7.16
//
#include <easyx.h>
#include <stdio.h>
#include <time.h>
#include <vector>
using std::vector;
#define SQR(x) ((x)*(x))
#define DISTANCE(x1, y1, x2, y2) sqrt(SQR(x2-x1)+SQR(y2-y1))
struct FloatPoint
{
double x = 0, y = 0;
};
struct MovePoint
{
FloatPoint pt = {}; // 原位置
double vx = 0, vy = 0; // 毫秒速度
bool isRedundant = false; // 该点是否多余(Src)
bool isUsed = false; // 是否已被作为目标点(Dst)
};
IMAGE imgSrc, imgDst;
vector<MovePoint> vecSrc, vecDst;
int nAnimateDuration = 2; // 动画时长(秒)
int nFPS = 24; // 帧率
int nDelayMS = (int)((1.0 / nFPS) * 1000); // 每帧的延时(毫秒)
void Load()
{
WCHAR lpszSrcFile[128] = {};
WCHAR lpszDstFile[128] = {};
printf("Source file:");
_getws_s(lpszSrcFile);
printf("Destination file:");
_getws_s(lpszDstFile);
loadimage(&imgSrc, lpszSrcFile);
loadimage(&imgDst, lpszDstFile);
}
void Record()
{
IMAGE* pImg[] = { &imgSrc,&imgDst };
vector<MovePoint>* pVec[] = { &vecSrc,&vecDst };
for (int i = 0; i < 2; i++)
{
SetWorkingImage(pImg[i]);
for (int x = 0; x < getwidth(); x++)
{
for (int y = 0; y < getheight(); y++)
{
if (getpixel(x, y) == BLACK)
{
MovePoint pt = { (double)x,(double)y };
pVec[i]->push_back(pt);
}
}
}
}
}
void Calc()
{
// 平衡源点和目标点数量
size_t sizeSrc = vecSrc.size();
size_t sizeDst = vecDst.size();
if (sizeSrc < sizeDst)
{
for (size_t i = 0; i < sizeDst - sizeSrc; i++)
{
vecSrc.push_back(vecSrc[rand() % sizeSrc]);
}
}
else if (sizeSrc > sizeDst)
{
size_t sizeDiff = sizeSrc - sizeDst;
for (size_t i = 0; i < sizeDiff; i++)
{
vecSrc[sizeSrc / sizeDiff * i].isRedundant = true;
}
}
// 分配目标点,计算速度
size_t sizeSrcNew = vecSrc.size();
for (size_t i = 0; i < sizeSrcNew; i++)
{
FloatPoint ptBest = { -1,-1 };
double dDistance = -1;
int nBestIndex = -1;
for (size_t j = 0; j < sizeDst; j++)
{
// 多余的点选哪个做目标都可以,但是其他点只能选择没用过的
if (vecSrc[i].isRedundant || !vecDst[j].isUsed)
{
double dThis = DISTANCE(
vecSrc[i].pt.x, vecSrc[i].pt.y,
vecDst[j].pt.x, vecDst[j].pt.y);
if (dThis < dDistance || dDistance == -1)
{
ptBest = vecDst[j].pt;
dDistance = dThis;
nBestIndex = j;
}
}
}
if (nBestIndex == -1)
{
printf("\nError.\n");
exit(-1);
}
if (!vecSrc[i].isRedundant)
{
vecDst[nBestIndex].isUsed = true;
}
vecSrc[i].vx = (ptBest.x - vecSrc[i].pt.x) / (double)nAnimateDuration / 1000;
vecSrc[i].vy = (ptBest.y - vecSrc[i].pt.y) / (double)nAnimateDuration / 1000;
}
}
// 精确延时函数(可以精确到 1ms,精度 ±1ms)
// by yangw80<yw80@qq.com>, 2011-5-4
void HpSleep(int ms)
{
static clock_t oldclock = clock(); // 静态变量,记录上一次 tick
oldclock += ms * CLOCKS_PER_SEC / 1000; // 更新 tick
if (clock() > oldclock) // 如果已经超时,无需延时
oldclock = clock();
else
while (clock() < oldclock) // 延时
Sleep (1); // 释放 CPU 控制权,降低 CPU 占用率
// Sleep (0); // 更高精度、更高 CPU 占用率
}
void Animate(bool reverse = false)
{
int nDelayFrequency = nAnimateDuration * 1000 / nDelayMS;
vector<MovePoint> vecPt = vecSrc;
if (reverse)
{
for (size_t j = 0; j < vecSrc.size(); j++)
{
vecPt[j].pt.x += vecPt[j].vx * nAnimateDuration * 1000;
vecPt[j].pt.y += vecPt[j].vy * nAnimateDuration * 1000;
vecPt[j].vx = -vecPt[j].vx;
vecPt[j].vy = -vecPt[j].vy;
}
}
for (int i = 0; i < nDelayFrequency; i++)
{
cleardevice();
for (size_t j = 0; j < vecSrc.size(); j++)
{
vecPt[j].pt.x += vecPt[j].vx * nDelayMS;
vecPt[j].pt.y += vecPt[j].vy * nDelayMS;
putpixel((int)vecPt[j].pt.x, (int)vecPt[j].pt.y, BLACK);
}
FlushBatchDraw();
HpSleep(nDelayMS);
}
}
int main()
{
Load();
printf("\nLoading...");
Record();
Calc();
initgraph(640, 480);
BeginBatchDraw();
setbkcolor(WHITE);
settextcolor(BLACK);
while (true)
{
cleardevice();
putimage(0, 0, &imgSrc);
outtextxy(0, 0, L"Any key to start.");
FlushBatchDraw();
flushmessage();
getmessage(EM_KEY);
Animate();
outtextxy(0, 0, L"Any key to replay.");
FlushBatchDraw();
flushmessage();
getmessage(EM_KEY);
Animate(true);
}
EndBatchDraw();
closegraph();
return 0;
}