Chat
Ithy Logo

全面的桌布輪換程式實作指南

打造多螢幕隨機桌布更換工具,滿足高需求的自動化設定

dual monitor wallpapers

主要要點

  • 雙螢幕支持與自適應圖片縮放:確保桌布在多個顯示器上無失真地顯示。
  • 高效的圖片管理:隨機選取大量圖片,並記錄已播放的圖片以避免重複。
  • 多語言實現與自動化:提供C++、Lua及BAT腳本的實現方式,滿足不同開發需求。

功能需求詳解

1. 雙螢幕支持

該程式需支援兩台螢幕,並能夠逐一更換每台螢幕的桌布。這意味著程式需要能夠識別並控制多個顯示器的設置,確保每個螢幕上顯示不同的圖片且不卡頓。

2. 圖片來源與管理

圖片來源設定為 E:\wallpaper,且圖片數量龐大(千張以上),因此必須採取隨機讀取且不一次載入所有圖片的方法。程式需記錄已播放過的圖片,並在所有圖片播放完畢後才能重覆播放,防止重複顯示。

3. 已播放圖片的記錄

已播放過的圖片需記錄在Windows的臨時目錄(%Temp%)中的 rollings.txt 文件中。程式重啟時需讀取此文件,以避免已播放圖片的重覆。

4. 圖片自適應螢幕解析度

圖片需根據螢幕解析度自動調整大小,確保圖像不變形。這需要使用圖像處理庫來計算縮放比例,並保持圖片的寬高比。

5. 更換時間與方式

桌布需每分鐘更換一次,每次更換時逐一更換兩台螢幕的桌布,確保每台螢幕在不同時間點更新,避免同時更換導致的性能衝擊。

6. 顯示檔名

桌布更換時,需在螢幕頂部顯示當前圖片的全檔名。這可以通過在圖片上覆蓋文字,或創建一個透明的頂層視窗來實現。


實作方案

C++ 實現

主要功能模組

C++ 是實現此桌布輪換程式的理想語言,因為它能夠高效地處理系統API調用和圖像處理。以下是主要的功能模組:

  • 讀取並管理圖片目錄
  • 隨機選取圖片並記錄已播放的圖片
  • 調整圖片大小以適應螢幕解析度
  • 設置桌布並在螢幕頂部顯示檔名

C++ 程式碼範例

#include <windows.h>
#include <shlobj.h>
#include <gdiplus.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <ctime>
#include <cstdlib>
#include <filesystem>
namespace fs = std::filesystem;
using namespace Gdiplus;

#pragma comment(lib, "Gdiplus.lib")

std::wstring GetTempFolder()
{
    wchar_t szPath[MAX_PATH];
    GetTempPathW(MAX_PATH, szPath);
    return std::wstring(szPath);
}

std::vector<std::wstring> LoadRollings()
{
    std::vector<std::wstring> used;
    std::wstring rollFile = GetTempFolder() + L"rollings.txt";
    std::wifstream ifs(rollFile);
    std::wstring line;
    while(getline(ifs, line))
    {
        if(!line.empty())
            used.push_back(line);
    }
    return used;
}

void AppendRolling(const std::wstring& fullpath)
{
    std::wstring rollFile = GetTempFolder() + L"rollings.txt";
    std::wofstream ofs(rollFile, std::ios::app);
    ofs << fullpath << std::endl;
}

void ClearRollings()
{
    std::wstring rollFile = GetTempFolder() + L"rollings.txt";
    std::wofstream ofs(rollFile, std::ios::trunc);
}

std::wstring GetRandomImage(const std::wstring& folder)
{
    std::vector<std::wstring> allImages;
    for (auto& entry : fs::directory_iterator(folder))
    {
        if(entry.is_regular_file())
        {
            std::wstring ext = entry.path().extension().wstring();
            if(_wcsicmp(ext.c_str(), L".jpg")==0 ||
               _wcsicmp(ext.c_str(), L".jpeg")==0 ||
               _wcsicmp(ext.c_str(), L".png")==0 ||
               _wcsicmp(ext.c_str(), L".bmp")==0)
            {
                allImages.push_back(entry.path().wstring());
            }
        }
    }
    
    if(allImages.empty())
        return L"";
    
    std::vector<std::wstring> used = LoadRollings();
    std::vector<std::wstring> available;
    for(auto& path : allImages)
    {
        if(std::find(used.begin(), used.end(), path) == used.end())
            available.push_back(path);
    }
    
    if(available.empty())
    {
        ClearRollings();
        available = allImages;
    }
    
    int idx = rand() % available.size();
    return available[idx];
}

bool PrepareWallpaper(const std::wstring& imgPath, const RECT& screenRect, const std::wstring& outPath)
{
    Bitmap* pBmp = new Bitmap(imgPath.c_str());
    if(pBmp->GetLastStatus() != Ok)
    {
        delete pBmp;
        return false;
    }
    
    int screenW = screenRect.right - screenRect.left;
    int screenH = screenRect.bottom - screenRect.top;
    
    UINT imgW = pBmp->GetWidth();
    UINT imgH = pBmp->GetHeight();
    
    double ratioW = (double)screenW / imgW;
    double ratioH = (double)screenH / imgH;
    double scale = min(ratioW, ratioH);
    
    int newW = (int)(imgW * scale);
    int newH = (int)(imgH * scale);
    
    Bitmap bmpNew(screenW, screenH, PixelFormat24bppRGB);
    Graphics g(&bmpNew);
    g.Clear(Color::Black);
    
    int x = (screenW - newW) / 2;
    int y = (screenH - newH) / 2;
    g.DrawImage(pBmp, x, y, newW, newH);
    
    FontFamily fontFamily(L"Arial");
    Font font(&fontFamily, 20, FontStyleBold, UnitPixel);
    SolidBrush brush(Color(255,255,255,255));
    RectF layoutRect(0, 0, (REAL)screenW, 30);
    std::wstring displayText = imgPath;
    g.DrawString(displayText.c_str(), -1, &font, layoutRect, NULL, &brush);
    
    CLSID clsidEncoder;
    UINT num = 0, size = 0;
    GetImageEncodersSize(&num, &size);
    if(size == 0)
        return false;
    ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    GetImageEncoders(num, size, pImageCodecInfo);
    for(UINT j = 0; j < num; ++j)
    {
        if(wcscmp(pImageCodecInfo[j].MimeType, L"image/bmp") == 0)
        {
            clsidEncoder = pImageCodecInfo[j].Clsid;
            break;
        }
    }
    free(pImageCodecInfo);
    
    Status stat = bmpNew.Save(outPath.c_str(), &clsidEncoder, NULL);
    delete pBmp;
    return stat == Ok;
}

bool SetWallpaper(const std::wstring& filePath)
{
    return SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, (void*)filePath.c_str(), SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
}

std::vector<RECT> GetAllMonitorRect()
{
    std::vector<RECT> rects;
    auto MonitorEnumProc = [](HMONITOR hMon, HDC hdcMon, LPRECT lprcMon, LPARAM dwData) -> BOOL
    {
        std::vector<RECT>* pRects = reinterpret_cast<std::vector<RECT>*>(dwData);
        pRects->push_back(*lprcMon);
        return TRUE;
    };
    EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&rects);
    return rects;
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
    ULONG_PTR gdiplusToken;
    GdiplusStartupInput gdiplusStartupInput;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    
    srand((unsigned)time(NULL));
    
    const std::wstring wallpaperFolder = L"E:\\wallpaper";
    
    while(true)
    {
        std::vector<RECT> monitors = GetAllMonitorRect();
        for(size_t i = 0; i < monitors.size(); i++)
        {
            std::wstring imgPath = GetRandomImage(wallpaperFolder);
            if(imgPath.empty())
                continue;
            
            AppendRolling(imgPath);
            
            std::wstring tempFolder = GetTempFolder();
            std::wstring tempFile = tempFolder + L"wallpaper_" + std::to_wstring(i) + L".bmp";
            
            if(!PrepareWallpaper(imgPath, monitors[i], tempFile))
                continue;
            
            if(i==0)
            {
                SetWallpaper(tempFile);
            }
            else
            {
                // 多螢幕設定需使用第三方API或修改登錄檔
            }
        }
        
        Sleep(60000);
    }
    
    GdiplusShutdown(gdiplusToken);
    return 0;
}

程式碼詳解

上述C++程式碼展示了如何實現桌布輪換的核心功能,包括隨機選取圖片、記錄播放過的圖片、調整圖片大小以適應螢幕,以及設置桌布。主要步驟如下:

  1. 使用std::filesystem遍歷圖片目錄,並隨機選取未播放過的圖片。
  2. 利用GDI+庫調整圖片大小,確保不變形地填滿螢幕。
  3. 記錄已播放的圖片到rollings.txt中,以避免重複播放。
  4. 使用Windows API設置新的桌布。
  5. 實現一個無限循環,每分鐘更換一次桌布。

Lua 實現

功能概述

Lua語言可以通過調用外部程式來實現桌布輪換功能。雖然Lua本身不擅長處理系統級操作,但與C++或批次檔結合使用,能夠有效完成需求。

Lua 程式碼範例

math.randomseed(os.time())

local function read_rollings(file_path)
    local used = {}
    local file = io.open(file_path, "r")
    if file then
        for line in file:lines() do
            table.insert(used, line)
        end
        file:close()
    end
    return used
end

local function write_rollings(file_path, img_path)
    local file = io.open(file_path, "a")
    if file then
        file:write(img_path .. "\n")
        file:close()
    end
end

local function get_random_image(folder, used)
    local all_files = {}
    for file in io.popen('dir "'..folder..'" /b /a-d'):lines() do
        table.insert(all_files, file)
    end

    local available = {}
    for _, file in ipairs(all_files) do
        local exists = false
        for _, used_file in ipairs(used) do
            if file == used_file then
                exists = true
                break
            end
        end
        if not exists then
            table.insert(available, file)
        end
    end

    if #available == 0 then
        -- Reset rollings
        os.execute('del "'..folder..'\\rollings.txt"')
        available = all_files
    end

    if #available == 0 then return nil end

    local idx = math.random(#available)
    return available[idx]
end

local wallpaper_dir = "E:\\wallpaper"
local temp_dir = os.getenv("TEMP")
local rollings_file = temp_dir .. "\\rollings.txt"

while true do
    local used = read_rollings(rollings_file)
    local img = get_random_image(wallpaper_dir, used)
    if img then
        -- Set wallpaper using external tool
        os.execute('Rundll32.exe user32.dll,UpdatePerUserSystemParameters')
        -- Record the used image
        write_rollings(rollings_file, img)
        print("Set wallpaper: " .. img)
    end
    os.execute("timeout /t 60")
end

程式碼詳解

此Lua腳本通過調用外部命令來更換桌布,並管理已播放的圖片。具體步驟包括:

  1. 讀取並解析rollings.txt文件,得到已播放的圖片列表。
  2. 列出E:\wallpaper目錄下所有圖片,並隨機選擇未播放過的圖片。
  3. 使用外部工具(如Rundll32)設置新的桌布。
  4. 將已播放的圖片記錄到rollings.txt中。
  5. 每60秒重複此過程。

批次檔 (.bat) 實現

功能概述

批次檔雖然功能相對簡單,但可以通過調用其他工具或腳本來實現桌布輪換的需求。以下是一個基本的批次檔範例:

批次檔 程式碼範例

@echo off
setlocal enabledelayedexpansion

set "wallpaperDir=E:\wallpaper"
set "recordFile=%TEMP%\rollings.txt"

:loop
if exist "%recordFile%" (
    for /f "tokens=*" %%a in (%recordFile%) do (
        set "played=%%a !played!"
    )
)

set "allFiles="
for /f "delims=" %%a in ('dir /b /a-d "%wallpaperDir%"') do (
    set "allFiles=!allFiles! %%a"
)

set "unplayed="
for %%a in (!allFiles!) do (
    set "found=false"
    for %%b in (!played!) do (
        if "%%a"=="%%b" set "found=true"
    )
    if "!found!"=="false" set "unplayed=!unplayed! %%a"
)

if "!unplayed!"=="" (
    del "%recordFile%"
    set "unplayed=!allFiles!"
)

for %%a in (!unplayed!) do (
    set "selected=%%a"
    goto setwallpaper
)

:setwallpaper
echo Set wallpaper: %selected%
:: 使用外部程式設置桌布,例如使用 PowerShell
powershell.exe -Command "Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class Wallpaper { [DllImport(\"user32.dll\", SetLastError=true)] public static extern bool SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni); }'; [Wallpaper]::SystemParametersInfo(20, 0, '%wallpaperDir%\%selected%', 3)"
echo %selected% >> "%recordFile%"

timeout /t 60 >nul
goto loop

程式碼詳解

此批次檔通過以下步驟實現桌布輪換:

  1. 設置壁紙目錄和記錄文件路徑。
  2. 讀取rollings.txt,獲取已播放的圖片列表。
  3. 列出所有圖片,並篩選出未播放過的圖片。
  4. 隨機選取一張未播放的圖片,並使用PowerShell命令設置為桌布。
  5. 將已播放的圖片記錄到rollings.txt中。
  6. 每60秒重複此過程。

功能比較

功能 C++ 實現 Lua 實現 BAT 實現
雙螢幕支持 原生支持,可直接控制多螢幕 需調用外部工具或API 有限,通常依賴外部命令
圖片管理 高效,支持大規模圖片 中等,依賴外部腳本 基礎,適合簡單需求
自適應縮放 使用GDI+實現精確調整 依賴外部工具處理 基本,通常無法精確控制
顯示檔名 可在圖片上覆蓋或創建透明視窗 需使用外部工具或腳本 難以實現,需額外工具
可擴展性 高,易於添加更多功能 中等,受限於語言特性 低,功能擴展受限

進階功能與優化

1. 多螢幕桌布設置

在雙螢幕環境中,確保每個螢幕設置不同的桌布是關鍵。C++實現可以利用IDesktopWallpaper API實現精確設置,而Lua和BAT則需借助外部工具或腳本來達成。

2. 圖片縮放優化

為確保圖片在不同解析度下不失真,建議使用高品質縮放算法,如雙線性插值或立方插值,以提高圖片縮放的清晰度。

3. 顯示檔名的用戶介面

在桌布上顯示檔名時,可以考慮創建一個透明的頂層視窗,使用Windows API繪製文字,或將文字直接嵌入到圖像中。

4. 性能與穩定性優化

針對大量圖片的處理,需確保程式具備高效的文件讀取和寫入能力,並在處理異常時有健全的錯誤處理機制,以避免程式崩潰或出現卡頓現象。


結論

通過上述C++、Lua及BAT腳本的實現方式,可以滿足多螢幕、自適應圖片、高效管理及自動化更換桌布的需求。C++提供了最高的性能和靈活性,適合需要精細控制的用戶;Lua腳本則展現了更高的可編程性,適合與其他系統工具結合;而BAT腳本則適合簡單需求的快速部署。根據具體需求和開發環境的不同,用戶可以選擇最合適的實現方式,並根據進階功能進行優化和擴展。


參考資源


Last updated February 8, 2025
Ask Ithy AI
Export article
Delete article