Skip to content
This repository has been archived by the owner on Aug 1, 2024. It is now read-only.

Latest commit

 

History

History
208 lines (156 loc) · 9.7 KB

README.adoc

File metadata and controls

208 lines (156 loc) · 9.7 KB

wallpaper master

起源

最近一直在折腾一下壁纸的东西,前段时间刚写了一个跨平台桌面(windows/linux kde)的壁纸网络应用,个人使用效果还不错的样子。

前两天突然发现了wallpaper engine这个软件,然后在创意工坊中打开了新世界,感觉挺炫酷的。于是想着看自己能不能照着实现一些类似的特性。于是就有了这个工程。

预览

操作
效果
动态scene包

特性

  • 无干扰的独立壁纸层

  • 鼠标跟随实时去衣

  • scene数据包制作与动态效果实现

本工程基于QT实现,涉及到一些Windows的原生API操作,请查看微软doc文档。

无干扰的独立壁纸层

这个大佬们已经分析的很透彻了,就是让自己的壁纸画布成为桌面壁纸窗口的子窗口,这样就可以让自己的壁纸覆盖windows自带的壁纸,同时有达到不干扰windows自带壁纸的目的。当然还有一个最重要的目的就是自己的壁纸层可以随意操作。

窗口结构

上图中,2是windows自带的壁纸窗口(或许这个说法并不准确),我们只需要将我们的壁纸窗口设置成2窗口的子窗口就达到了目的,图中3窗口就是我们自己的壁纸窗口。

然而窗口2并不太好查找,它并没有设计窗口名称,我们只知道它的类名是WorkerW,但是类名是WorkerW的窗口有很多,通过对比我们发现,我们要查找的2窗口的父窗口是图中的1窗口。而1窗口我们通过类名的窗口名很容易查找到。至此,我们查找窗口2的思路就确定了:

  1. 先通过类名和窗口名查找到窗口1

HWND hwnd = ::FindWindowA("progman","Program Manager");

然后循环遍历查找类名为WorkerW,并判断查找到的窗口的父窗口,是不是上面确定的hwnd窗口,如果是的,那么查找就结束了。

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窗口。

窗口结构

具体的原因可以参考 下面这篇文章 的解释。这篇文章中也给出了处理方法,就是通过SendMessage函数向progman窗口发送0x052C的消息。这时候 Program 窗口就产生两个WorkerW窗口。我们就可以找到我们需要的窗口了。

接着我们需要将我们的壁纸画布窗口设置成background的子窗口,并将画布窗口显示出来

HWND current = (HWND)m_mask->winId();
SetParent(current,background);
m_mask->show();

这样我们的第一个目标就达成了。

鼠标跟随实时去衣

虽然我们无法准确的确定wallpaper engine的鼠标动态去衣是怎样实现的。但是我们可以根据实现出来的效果反推可能的操作细节。通过推测,我得出这样的结论。

  • 有两张图片,一张是带有裸漏部分,另外一张是局部地方有衣服的像素,其他的地方就是全透明的,这两张图片可以参考ps的图层效果

  • 将两张图片按照合理顺序叠加(类比图层显示叠加),就可以显示带衣服的完整效果。而如果将上层的衣服区域透明化,就显示出了底层的裸漏部分。

我们实现的细节就确定了。

  • 先绘制底层图片。

  • 确定鼠标位置,并根据鼠标位置以一定半径确定一个圆形区域。

  • 绘制上层图片,并将上层图片在圆形区域部分的透明度设置为较小值,或者干脆直接设置为全透明。

  • 鼠标移动时,实时计算圆形区域,并刷新壁纸层图像。

此处遇到的第一个问题就是壁纸层我们没法聚焦,甚至没法捕捉到鼠标的事件。所以通过重新鼠标事件的方法不可行。这时候我们就要使用windows的事件钩子函数,来注册全局的事件钩子。

mouseHook =SetWindowsHookEx( WH_MOUSE_LL,mouseProc,GetModuleHandle(NULL),NULL);//注册鼠标钩子

然后在钩子函数中确定鼠标的实时位置,并计算以鼠标为中心的圆形区域,然后刷新壁纸窗口。

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自带窗口上面的三大金刚键。

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算法部分

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;
}

这是合成算法部分

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包