使用位图处理和重绘分层窗口的正确方法

问题描述

我正在制作一个在完全透明的窗口中显示图像的脚本,因此它只是屏幕上的图像本身,您可以移动它。结果如下:

example

简化代码:

#include <math.h>
#include <windows.h>
#include <windowsx.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")


HWND hWnd;

double imageZoom = 0.5;

VOID OnPaint(HWND hWnd,HDC hdc)
{
    Bitmap image(L"example.png");

    // Get image dimensions
    int imageWidth = image.GetWidth();
    int imageHeight = image.GetHeight();

    // Update the dimensions using the image zoom
    imageWidth = static_cast<int>(floor((double)imageWidth * imageZoom));
    imageHeight = static_cast<int>(floor((double)imageHeight * imageZoom));

    // Make a DC on which to draw the image
    HDC drawingDC = CreateCompatibleDC(hdc);
    
    // Create a new bitmap for drawing on it
    HBITMAP newBitmap = CreateCompatibleBitmap(hdc,imageWidth,imageHeight);

    // Select the new bitmap to draw on it and save the old one
    HBITMAP oldBitmap = (HBITMAP)SelectObject(drawingDC,newBitmap);
    
    // Draw image to the newly created DC
    Graphics graphics(drawingDC);
    Rect point(0,imageHeight);
    graphics.DrawImage(&image,point);

    // Create a blend function
    BLENDFUNCTION blend = { 0 };
    blend.BlendOp = AC_SRC_OVER;
    blend.SourceConstantAlpha = 255;
    blend.AlphaFormat = AC_SRC_ALPHA;

    // Set window info
    RECT coord;
    GetWindowRect(hWnd,&coord);
    POINT windowPosition = { coord.left,coord.top };
    SIZE windowSize = { imageWidth,imageHeight };
    POINT imagePosition = { 0,0 };

    // Call UpdateLayeredWindow
    UpdateLayeredWindow(hWnd,hdc,&windowPosition,&windowSize,drawingDC,&imagePosition,&blend,ULW_ALPHA);

    SelectObject(drawingDC,oldBitmap);
    DeleteObject(newBitmap);
    DeleteDC(drawingDC);
    ReleaseDC(NULL,hdc);
}

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

INT WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,PSTR,INT iCmdShow)
{
    // Initialize GDI+.
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR           gdiplusToken;
    GdiplusStartup(&gdiplusToken,&gdiplusStartupInput,NULL);


    MSG                 msg;
    WNDCLASS            wndClass;

    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WndProc;
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hInstance;
    wndClass.hIcon = 0;
    wndClass.hCursor = LoadCursor(NULL,IDC_ARROW);
    wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = TEXT("Sticker");

    RegisterClass(&wndClass);

    hWnd = CreateWindowEx(
        WS_EX_LAYERED,TEXT("Sticker"),// window class name
        TEXT("Sticker"),// window caption
        NULL,// window style
        0,// initial x position
        0,// initial y position
        500,// initial x size
        500,// initial y size
        NULL,// parent window handle
        NULL,// window menu handle
        hInstance,// program instance handle
        NULL);                    // creation parameters

    ShowWindow(hWnd,iCmdShow);

    while (GetMessage(&msg,NULL,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    GdiplusShutdown(gdiplusToken);
    return msg.wParam;
}  // WinMain
bool holding = false;
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    HDC          hdc;
    PAINTSTRUCT  ps;
    
    switch (message)
    {
    case WM_CREATE:
        hdc = BeginPaint(hWnd,&ps);
        OnPaint(hWnd,hdc);
        EndPaint(hWnd,&ps);
        return 0;
    case WM_LBUTTONDOWN:
        // In short,this allows moving the image
        PostMessage(hWnd,WM_NCLBUTTONDOWN,2,0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd,&ps);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_MBUTTONDOWN:
        // Just to test the size changing
        imageZoom += 0.1;
        RedrawWindow(hWnd,RDW_INTERNALPAINT);
        return 0;
    case WM_RBUTTONDOWN:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hWnd,message,wParam,lParam);
    }
} // WndProc

我不确定这是否是使用 DC 和 RedrawWindow() 的正确方法。问题是,如果我 RedrawWindow() 使用新的图像大小(我包含了 WM_MBUTTONDOWN 消息来测试),图像会更新并使用新大小绘制,但分层窗口的大小似乎保持不变不知何故相同。这是它的样子:

example2

我不知道为什么会这样,所以也许有人知道为什么。

但主要的问题是,创建、更新和渲染分层窗口的正确方法是什么?我想这种方式不好,但我在互联网上找不到任何其他可以理解的例子

更新:

我已经更新了代码。现在它不使用 WM_PAINT 消息,只在需要时使用 UpdateLayeredWindow。

#include <math.h>
#include <windows.h>
#include <windowsx.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")


#include <wincodec.h>
#include <wincodecsdk.h>
#pragma comment(lib,"WindowsCodecs.lib")


HWND hWnd;

Bitmap* image;

double imageZoom = 0.5;

int originalWidth;
int originalHeight;

void firstUpdateLayeredWindow()
{
    image = Bitmap::FromFile(L"example.png");

    // Get image dimensions
    originalWidth = image->GetWidth();
    originalHeight = image->GetHeight();

    // Update the dimensions using the image zoom
    int imageWidth = static_cast<int>(floor((double)originalWidth * imageZoom));
    int imageHeight = static_cast<int>(floor((double)originalHeight * imageZoom));

    HDC hdc = GetDC(NULL);

    // Make a DC which which hold the bitmap for drawing
    HDC drawingDC = CreateCompatibleDC(hdc);

    // Create a new bitmap for drawing on it
    HBITMAP newBitmap = CreateCompatibleBitmap(hdc,imageHeight);

    // Select the new bitmap onto the drawing DC and save the old one
    HBITMAP oldBitmap = (HBITMAP)SelectObject(drawingDC,newBitmap);

    // Draw image to the newly created DC
    Graphics graphics(drawingDC);
    Rect point(0,imageHeight);
    graphics.DrawImage(image,hdc);
}

void followingUpdateLayeredWindow()
{
    // Update the dimensions using the image zoom
    int imageWidth = static_cast<int>(floor((double)originalWidth * imageZoom));
    int imageHeight = static_cast<int>(floor((double)originalHeight * imageZoom));

    HDC hdc = GetDC(NULL);
    
    // Make a DC on which to draw the image
    HDC drawingDC = CreateCompatibleDC(hdc);

    // Create a new bitmap for drawing on it
    HBITMAP newBitmap = CreateCompatibleBitmap(hdc,point);
    

    // Create a blend function
    BLENDFUNCTION blend = { 0 };
    blend.BlendOp = AC_SRC_OVER;
    blend.SourceConstantAlpha = 255;
    blend.AlphaFormat = AC_SRC_ALPHA;

    // Set window info
    RECT coord;
    GetWindowRect(hWnd,NULL);
    
    MSG                 msg;
    WNDCLASS            wndClass;

    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WndProc;
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hInstance;
    wndClass.hIcon = 0;
    wndClass.hCursor = LoadCursor(NULL,IDC_ARROW);
    wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = TEXT("Sticker");

    RegisterClass(&wndClass);

    hWnd = CreateWindowEx(
        WS_EX_LAYERED,// window caption
        WS_DLGFRAME,// program instance handle
        NULL);                    // creation parameters


    // Update the layered window for the first time,attaching a bitmap to it
    firstUpdateLayeredWindow();

    ShowWindow(hWnd,LPARAM lParam)
{
    switch (message)
    {
    case WM_LBUTTONDOWN:
        // In short,0);
        return 0;
    case WM_MBUTTONDOWN:
    {
        // Just to test the size changing
        imageZoom += 0.1;

        // Update the layered window with the function for updating it multiple times
        followingUpdateLayeredWindow();
    }
        return 0;
    case WM_RBUTTONDOWN:
        PostQuitMessage(0);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hWnd,lParam);
    }
} // WndProc

不幸的是,它仍然无法正常工作。图像已正确调整大小,但结果与之前的屏幕截图相同(图像被剪裁)。它仅在增加图像大小时发生,这就是为什么我认为问题在于分层窗口本身的大小。更奇怪的是,如果我增加 imageZoom 变量很多,然后移动窗口,它实际上会出于某种原因自行更新并以实际大小绘制整个图像,但这只会发生在窗口传递某个值什么的。

在更新附加到它的位图后,我什至尝试使用单独的 UpdateLayeredWindow() 函数更新分层窗口的大小。

更新:已解决但不知道为什么

我通过在分层窗口中包含 WS_POPUP 样式解决了图像被剪裁的奇怪行为。现在它完美运行,虽然我不知道为什么

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)