Wpmaster
No description available
Install / Use
/learn @qiuzhiqian/WpmasterREADME
= wallpaper master
== 起源 最近一直在折腾一下壁纸的东西,前段时间刚写了一个跨平台桌面(windows/linux kde)的壁纸网络应用,个人使用效果还不错的样子。
https://github.com/qiuzhiqian/wallpaper[地址]
前两天突然发现了wallpaper engine这个软件,然后在创意工坊中打开了新世界,感觉挺炫酷的。于是想着看自己能不能照着实现一些类似的特性。于是就有了这个工程。
https://github.com/qiuzhiqian/wpmaster[本项目地址]
== 预览
image::https://raw.githubusercontent.com/qiuzhiqian/wpmaster/master/doc/image/review_1.gif[操作] image::https://raw.githubusercontent.com/qiuzhiqian/wpmaster/master/doc/image/review_2.gif[效果] image::https://raw.githubusercontent.com/qiuzhiqian/wpmaster/master/doc/image/review_3.gif[动态scene包]
== 特性
- 无干扰的独立壁纸层
- 鼠标跟随实时去衣
- scene数据包制作与动态效果实现
本工程基于QT实现,涉及到一些Windows的原生API操作,请查看微软doc文档。
== 无干扰的独立壁纸层 这个大佬们已经分析的很透彻了,就是让自己的壁纸画布成为桌面壁纸窗口的子窗口,这样就可以让自己的壁纸覆盖windows自带的壁纸,同时有达到不干扰windows自带壁纸的目的。当然还有一个最重要的目的就是自己的壁纸层可以随意操作。
image::https://raw.githubusercontent.com/qiuzhiqian/wpmaster/master/doc/image/image_1.png[窗口结构]
上图中,2是windows自带的壁纸窗口(或许这个说法并不准确),我们只需要将我们的壁纸窗口设置成2窗口的子窗口就达到了目的,图中3窗口就是我们自己的壁纸窗口。
然而窗口2并不太好查找,它并没有设计窗口名称,我们只知道它的类名是WorkerW,但是类名是WorkerW的窗口有很多,通过对比我们发现,我们要查找的2窗口的父窗口是图中的1窗口。而1窗口我们通过类名的窗口名很容易查找到。至此,我们查找窗口2的思路就确定了:
- 先通过类名和窗口名查找到窗口1
[source.cpp] .... HWND hwnd = ::FindWindowA("progman","Program Manager"); ....
然后循环遍历查找类名为WorkerW,并判断查找到的窗口的父窗口,是不是上面确定的hwnd窗口,如果是的,那么查找就结束了。
[source.cpp] .... HWND background = NULL; HWND hwnd = ::FindWindowA("progman","Program Manager"); HWND worker = NULL; do{ worker = ::FindWindowExA(NULL,worker,"WorkerW",NULL); if(worker!=NULL){ char buff[200] = {0};
int ret = GetClassName(worker,(WCHAR*)buff,sizeof(buff)*2);
if(ret == 0){
int err = GetLastError();
qDebug()<<"err:"<<err;
}
//QString className = QString::fromUtf16((char16_t*)buff);
}
if(GetParent(worker) == hwnd){
background = worker;
}
}while(worker !=NULL); ....
上面代码中background就是我们查找到的目标窗口 但是默认情况下,我们可能根本找不到我们需要的这个WorkerW窗口。
image::https://raw.githubusercontent.com/qiuzhiqian/wpmaster/master/doc/image/image_2.png[窗口结构]
具体的原因可以参考 https://github.com/qiuzhiqian/wallpaper[下面这篇文章] 的解释。这篇文章中也给出了处理方法,就是通过SendMessage函数向progman窗口发送0x052C的消息。这时候 Program 窗口就产生两个WorkerW窗口。我们就可以找到我们需要的窗口了。
接着我们需要将我们的壁纸画布窗口设置成background的子窗口,并将画布窗口显示出来
[source.cpp] .... HWND current = (HWND)m_mask->winId(); SetParent(current,background); m_mask->show(); ....
这样我们的第一个目标就达成了。
== 鼠标跟随实时去衣
虽然我们无法准确的确定wallpaper engine的鼠标动态去衣是怎样实现的。但是我们可以根据实现出来的效果反推可能的操作细节。通过推测,我得出这样的结论。
- 有两张图片,一张是带有裸漏部分,另外一张是局部地方有衣服的像素,其他的地方就是全透明的,这两张图片可以参考ps的图层效果
- 将两张图片按照合理顺序叠加(类比图层显示叠加),就可以显示带衣服的完整效果。而如果将上层的衣服区域透明化,就显示出了底层的裸漏部分。
我们实现的细节就确定了。
- 先绘制底层图片。
- 确定鼠标位置,并根据鼠标位置以一定半径确定一个圆形区域。
- 绘制上层图片,并将上层图片在圆形区域部分的透明度设置为较小值,或者干脆直接设置为全透明。
- 鼠标移动时,实时计算圆形区域,并刷新壁纸层图像。
此处遇到的第一个问题就是壁纸层我们没法聚焦,甚至没法捕捉到鼠标的事件。所以通过重新鼠标事件的方法不可行。这时候我们就要使用windows的事件钩子函数,来注册全局的事件钩子。 [source.cpp] .... mouseHook =SetWindowsHookEx( WH_MOUSE_LL,mouseProc,GetModuleHandle(NULL),NULL);//注册鼠标钩子 .... 然后在钩子函数中确定鼠标的实时位置,并计算以鼠标为中心的圆形区域,然后刷新壁纸窗口。
[source.cpp] .... LRESULT CALLBACK mouseProc(int nCode,WPARAM wParam,LPARAM lParam ) { if(nCode == HC_ACTION) //当nCode等于HC_ACTION时,要求得到处理 { if(wParam==WM_MOUSEMOVE)//鼠标的移动 { POINT p; GetCursorPos(&p);//获取鼠标坐标 CMask* mask = w->getMask(); mask->setMask(p.x,p.y); //双薪壁纸 mask->update(); } } //qDebug()<<nCode<<","<<wParam<<","<<lParam; return CallNextHookEx(mouseHook,nCode,wParam,lParam);//返回给下一个钩子子程处理 } ....
第二个问题:由于我们的壁纸窗口设置成了windows原生hwnd的子窗口,导致我们通过点击窗口上面的关闭按钮是没办法结束整个进程的。所以我们需要自定义一个关闭按钮,并掩藏windows自带窗口上面的三大金刚键。
[source.cpp] .... this->setWindowFlags(Qt::FramelessWindowHint); ....
当然在这个设计过程中还遇到了一些其他的问题,比如显示两张图片是我最早使用的方案是用两个QLabel上下叠加来显示,但是这样在操作上很蛋疼。当然这些问题都顺利的解决掉了。
== scene数据包制作与动态效果实现 我们在使用wallpaper engine时,经常会发现一些动态图片,而这些动态图片并不是常规的gif动图,而是一个后缀名为pkg的数据包,官方称作为scene类型。这种数据类型在wallpaper engine中很常见。我们通过使用RePKG这个开源工具解开scene数据包,可以提取里面的一些基本的静态图片。通过提取出来的文件,我们大致猜测wallpaper engine的工作流程。
所谓动态图片无非就是一帧一帧的图片连续显示。 由于scene的图片动态范围和幅度一般都很小,如果我们保存原始的每一帧图片,那么压缩包会很大。由于相连帧的变化一般都很小,如果我们取相连帧的差值,并保存为图片,那么这些图像会存在大量的连续重复像素(以#000000为主),这样的图片是很容易无损压缩的。所以体积会比较小。
所以我们保存的图片只是一张最原始的图片(第一帧),然后就是第一帧和第二帧取diff值图片,然后就是第二帧和第三帧diff图片,一次类推。这样复原的时候我们只需要通过第一帧和第一个diff图片通过像素相加后取得第二张图,然后用第二张图和第二个diff图片通过像素相加后取得第三张图,一次类推就可以复原所有图片了。然后依次按照一定的时间间隔显示即可。
目前wallpaper engine的pkg包的diff规律还没有找到,我只能按照自己的理解来实现一个自己可用的算法,导致目前的软件跟wallpaper engine的scene包解析无法兼容。所以本软件的scene包需要自己手动制作(软件中集成了make制作按键,但是只能完成部分主要工作,还是需要人手动参与一部分操作,后续会完善制作工具),而无法直接导入官方scene包。
这是diff算法部分 [source.cpp] .... QColor CUtils::colorDiff(const QColor &clr1, const QColor &clr2){ quint8 red = quint8(clr1.red()) - quint8(clr2.red()); quint8 green = quint8(clr1.green()) - quint8(clr2.green()); quint8 blue = quint8(clr1.blue()) - quint8(clr2.blue());
return QColor(red,green,blue);
}
QImage CUtils::imageDiff(const QImage& img1,const QImage& img2){ QImage tmpImage(img1);
if(img1.size() != img2.size()){
return tmpImage;
}
int width = img1.width();
int height = img1.height();
for(int y=0;y<height;y++){
for(int x=0;x<width;x++){
tmpImage.setPixelColor(x,y,colorDiff(img1.pixelColor(x,y),img2.pixelColor(x,y)));
}
}
return tmpImage;
} ....
这是合成算法部分 [source.cpp] .... QColor CUtils::colorAdd(const QColor &clr1, const QColor &clr2){ quint8 red = quint8(clr1.red()) + quint8(clr2.red()); quint8 green = quint8(clr1.green()) + quint8(clr2.green()); quint8 blue = quint8(clr1.blue()) + quint8(clr2.blue());
return QColor(red,green,blue);
}
QImage CUtils::imageAdd(const QImage& img1,const QImage& img2){ QImage tmpImage(img1);
if(img1.size() != img2.size()){
return tmpImage;
}
int width = img1.width();
int height = img1.height();
for(int y=0;y<height;y++){
for(int x=0;x<width;x++){
tmpImage.setPixelColor(x,y,colorAdd(img1.pixelColor(x,y),img2.pixelColor(x,y)));
}
}
return tmpImage;
} ....
此处提供一个测试可用的scene包
https://github.com/qiuzhiqian/wpmaster[scene示例]
