首页 > 解决方案 > 使用位图处理和重绘分层窗口的正确方法

问题描述

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

例子

简化代码:

#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, 0, imageWidth, 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, 0, &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, 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);
        OnPaint(hWnd, hdc);
        EndPaint(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, NULL, NULL, RDW_INTERNALPAINT);
        return 0;
    case WM_RBUTTONDOWN:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
} // WndProc

我不确定这是否是使用 DC 和RedrawWindow(). 问题是,如果我RedrawWindow()使用新大小的图像(我包含一条 WM_MBUTTONDOWN 消息来测试它),图像会更新并使用新大小绘制,但分层窗口的大小似乎以某种方式保持不变。这是它的样子: 示例2

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

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

更新:

我已经更新了代码。现在它不使用 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, imageWidth, 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, 0, imageWidth, 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, 0, &blend, ULW_ALPHA);

    SelectObject(drawingDC, oldBitmap);
    DeleteObject(newBitmap);
    DeleteDC(drawingDC);
    ReleaseDC(NULL, 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, 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, 0, imageWidth, 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, 0, &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(BLACK_BRUSH);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = TEXT("Sticker");

    RegisterClass(&wndClass);

    hWnd = CreateWindowEx(
        WS_EX_LAYERED,
        TEXT("Sticker"),   // window class name
        TEXT("Sticker"),  // window caption
        WS_DLGFRAME,      // 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


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

    ShowWindow(hWnd, iCmdShow);

    while (GetMessage(&msg, NULL, 0, 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)
{
    switch (message)
    {
    case WM_LBUTTONDOWN:
        // In short, this allows moving the image
        PostMessage(hWnd, WM_NCLBUTTONDOWN, 2, 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, message, wParam, lParam);
    }
} // WndProc

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

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

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

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

标签: c++winapigdi+

解决方案


推荐阅读