在整个屏幕上进行手绘画的技术问答

4
我正在尝试制作一个简单的工具,使用户可以从正常操作模式切换到禁用所有应用程序消息并使用鼠标进行一些自由绘图的模式,然后再次切换模式以保留他们的绘图在屏幕上,同时进行其他正常操作。如果我决定,这可能会演变成一个不错的工具,您可以使用它来保存您所做的装饰并稍后加载它们。

当我开始这个项目(大约半年前,发现Windows API后不久)时,我只是全局跟踪鼠标,并将其画成GetDC(NULL)hdc中的圆形。当然,问题在于当其下方更新时,它会消失,并且仍会通过鼠标消息,因此,例如,如果我按住桌面上的按钮,它会在绘画中放置调整大小的矩形。

今天,自从6个月前进行了最后一次主要工作后,我终于有了一些空闲时间,决定重新制作并看看能否实现我想要的效果。我制作了一个透明、最上层、WS_CHILD、分层、最大化的窗口(基本上屏幕不会改变,但是有一个窗口在所有东西的顶部让消息通过)。接下来,我使它在绘画模式下,将alpha值设置为1,并允许用户绘画。我没有意识到的问题是,由于窗口的alpha值为1,所以绘画中的任何内容都不可见。

接下来,我尝试使用GetDC(NULL),但记得当某些东西更新时,它会被擦除。

现在我想使用位图和DC来重复地将屏幕存储到一个DC中,在另一个DC上绘制,然后带有未绘制部分的透明度将其复制回已存储屏幕的那个DC中,并将其复制回屏幕。但我感到有些泄气了。这是我的源代码(遮罩函数取自此教程)。如果有不必要的部分,请告诉我。我已经使用位图进行了双缓冲,但我不确定何时需要使用它们。

//Global mask since it takes longer to make
HBITMAP mask;

//Window Procedure Start
HDC screenDC; //hdc for entire screen
screenDC = GetDC (NULL); //get DC for screen

HDC memDC = CreateCompatibleDC (screenDC); //create DC for holding the screen+paint
HBITMAP bm = CreateCompatibleBitmap (screenDC, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN)); //create bitmap for memDC

HDC paintDC = CreateCompatibleDC (screenDC); //create DC to paint on
HBITMAP paintBM = CreateCompatibleBitmap (screenDC, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN)); //create bitmap for paintDC

SelectObject (memDC, bm); //select bitmap into memDC
SelectObject (paintDC, paintBM); //select painting bitmap into paintDC
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), screenDC, 0, 0, SRCCOPY); //copy screen to memDC
SetBkColor (paintDC, RGB(0,0,0)); //set background of paintDC to black so it's all transparent to start

//WM_CREATE
mask = CreateBitmapMask (bm, RGB(0,0,0)); //create black mask (paint colours are limited 1-255 now)

//painting is done into paintDC

//at end of Window Procedure
SelectObject (paintDC, mask); //select mask into paintDC
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCAND); //this in combination with the next should make it bitblt with all of the black taken out I thought
SelectObject (paintDC, paintBM); //select bitmaps into DCs
SelectObject (memDC, bm);
BitBlt (memDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCPAINT); //second part of transparent bitblt
BitBlt (screenDC, 0, 0, GetSystemMetrics (SM_CXSCREEN), GetSystemMetrics (SM_CYSCREEN), paintDC, 0, 0, SRCCOPY); //copy memDC back to screen

DeleteObject (paintBM); //delete stuff
DeleteObject (mask);
DeleteDC (memDC);
DeleteDC (paintDC);
ReleaseDC (hwnd, screenDC);

//CreateBitmapMask() (taken directly from http://www.winprog.org/tutorial/transparency.html
HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
HDC hdcMem, hdcMem2;
HBITMAP hbmMask;
BITMAP bm;

// Create monochrome (1 bit) mask bitmap.

GetObject(hbmColour, sizeof(BITMAP), &bm);
hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);

// Get some HDCs that are compatible with the display driver

hdcMem = CreateCompatibleDC(0);
hdcMem2 = CreateCompatibleDC(0);

SelectObject(hdcMem, hbmColour);
SelectObject(hdcMem2, hbmMask);

// Set the background colour of the colour image to the colour
// you want to be transparent.
SetBkColor(hdcMem, crTransparent);

// Copy the bits from the colour image to the B+W mask... everything
// with the background colour ends up white while everythig else ends up
// black...Just what we wanted.

BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

// Take our new mask and use it to turn the transparent colour in our
// original colour image to black so the transparency effect will
// work right.
BitBlt(hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT);

// Clean up.

DeleteDC(hdcMem);
DeleteDC(hdcMem2);

return hbmMask;
}

我知道代码可能很糟糕。我会考虑所有建议,只是我对这个主题不太清楚,不明白发生了什么,这使得修复变得困难。这段代码在我的电脑上运行,基本上每隔一段时间就会放置一个全屏黑色矩形。
我的主要问题是:有没有办法在屏幕上绘制而不会在底层窗口更新时被擦除?我现在唯一能想到的真正方法就是存储用户绘制的所有小线段的位置,并在屏幕顶部不断重绘它们。乍一看,这似乎非常低效和浪费内存。
另外,在写这篇文章时,我确信我不需要提供任何理论代码示例。大部分已经消失了,但这确实是一个理论问题。
编辑: 我刚刚发现了TransparentBlt函数,它似乎非常适合这种情况,所以我尝试使用它来替换SRCPAINT和SRCAND BitBlts,结果产生了相同的结果:一个黑色矩形覆盖了屏幕,有时当我的鼠标移动到其他地方时会消失一部分。
2个回答

3

最简单的方法可能是:

当处于非绘图模式时,使用SetLayeredWindowAttributes来为透明窗口设置“透明键”颜色。将窗口的 alpha 值设置为完全不透明,但使用该键色填充窗口(使用 FillRect 或类似方法),则整个窗口都会呈现为透明。然后,您在非键色中绘制的任何内容都将显示为实心,并覆盖在透明分层窗口下面的所有窗口上。

进入绘图模式的一种方法是创建一个新窗口,其中包含您透明图层下方的桌面捕获位图。或者避免使用位图,使其略微不透明并全部为实心颜色 - 例如,看起来像桌面被“变灰了”。关键是这个窗口不是完全透明的,因此它将能够接收鼠标输入,然后您可以使用该输入在实际透明图层上进行绘制。


乍一看,这个想法似乎很简单而又聪明!我从没想过使用两层窗口来达到我想要的效果,而且灰掉桌面也变得容易了。我之所以使用 LWA_ALPHA 是为了捕捉鼠标消息,因为 LWA_COLORKEY 无法实现,但在下面添加另一个窗口解决了一切问题。我可能会忙于学校作业而没有时间在几天内尝试它,但我会告诉你它的工作情况。 - chris

0

我认为最好的方法是在显示全屏内存DC内容的窗口之前,创建屏幕快照并将其保存为位图(以内存DC的形式)。这样,您实际上可以获取由单击等操作引起的消息,并像往常一样处理它们。

  1. 捕获屏幕内容
  2. 创建窗口(全屏)并使用捕获的内容
  3. 进行一些绘画
  4. 保存内容(作为bmp或任何花哨的格式)
  5. 关闭窗口并返回常规桌面

好主意吗?


我看到的一个问题是(除非我想错了),就是当他们切换回可以做其他事情的状态时,我无法保留他们绘制的内容(即这与透明度为1的分层窗口具有相同的效果,只是在我绘制时其下方的内容实时更新。如果我理解错了,请纠正我。当我回顾位图时,我想保存/加载的是paintBM-用户绘制的位图。然后可以将其放置在屏幕图像上方,然后将其应用回屏幕。 - chris
其实,如果我使用全局鼠标钩子,检查绘画模式是否开启,如果是的话,使用透明颜色(0,0,0)在分层窗口上绘制,并使用LWA_COLORKEY设置背景颜色为(0,0,0),然后从那里绘制(或向我的窗口发送适当的消息,因为它无法检测到它们),你认为这样行得通吗? - chris
2
在这种情况下,你有两个选择:1.创建一个常规窗口,捕获其下方的屏幕并每隔100毫秒更新一次。或者2.创建一个分层窗口,但不是为整个窗口设置alpha值为1,而是使用每个像素的alpha通道。我认为在这种情况下,选项2会更合适。但你需要弄清楚如何在分层窗口上使用每像素alpha通道透明度。所有的绘图都可以存储在具有每像素alpha通道的内存DC中,并在每个WM_PAINT中将其AlphaBlend到主窗口中。 - demorge
1
Hooking是不必要的,记住在使用AlphaBlend函数时颜色值应该预先乘以alpha值。 - demorge
我会尝试一下,只是需要学习如何使用它。我现在正在查看示例,很有可能直到明天上学才会将其整合进去 :p - chris
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接