截图方法生成黑色图像
Posted
技术标签:
【中文标题】截图方法生成黑色图像【英文标题】:Screenshot method generates black images 【发布时间】:2014-08-11 10:22:44 【问题描述】:在 c# 中使用 control.drawtobitmap 失败后,我的第二个选择是截取桌面截图并裁剪出所需的部分。 一旦我切换用户帐户,我的打嗝就会出现,虽然程序不会崩溃,但一旦用户切换,程序只会生成纯黑色图像。
我将此代码用作参考: WebBrowser.DrawToBitmap() or other methods?
我想从逻辑上讲这是有道理的,因为这将有助于 Windows 节省资源。
在我的情况下,我有哪些选择/解决方案?
编辑 1 对代码进行了修改以供测试:
int c = 0;
while (true)
try
c++;
Rectangle formBounds = this.Bounds;
Bitmap bmp = new Bitmap(formBounds.Width, formBounds.Height);
using (Graphics g = Graphics.FromImage(bmp))
g.CopyFromScreen(formBounds.Location, Point.Empty, formBounds.Size);
bmp.Save("picture" + c.ToString() + ".jpg");
Thread.Sleep(5000);
catch (Exception ex)
MessageBox.Show(ex.ToString());
这在用户帐户上工作得很好,但是一旦我切换用户,它就会返回异常:句柄无效。
有什么想法吗?
编辑 2:
DrawToBitmap 中的错误并非完全随机... 如果我使用了您提供的代码:
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
bmp.Save(".\\picture.jpg");
完美运行,例如:http://oi61.tinypic.com/1z23ynp.jpg
但是,当我右键单击网络浏览器控件时,DrawToBitmap 将返回一个空白图像。
示例:http://oi60.tinypic.com/9ay0yc.jpg
所以我可以通过添加轻松克服这个错误
((Control)webbrowser1).Enabled = false;
这使得在网络浏览器上的任何点击都是不可能的,但不幸的是,禁用它会使我的项目无用,因为它的主要功能是模拟鼠标在网络浏览器控件上的点击。 虽然如果窗口被隐藏,这也可能是一个问题。
目前我正在查看这篇文章,其中提供了代码来为您提供窗口句柄。
Simulate click into a hidden window 看来它可能有一些价值……看看吧。
【问题讨论】:
【参考方案1】:您在使用 DrawToBitmap 时遇到了什么问题? 它在这里工作正常,(W8.1,VS2013)即使使用 Webcontrol 并且在切换用户之后也是如此。 (但条件见最后的编辑!)
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
// Clipboard.SetImage(bmp); for testing only
bmp.Dispose();
以下是截取窗口屏幕截图的代码:
Rectangle formBounds = this.Bounds;
Bitmap bmp = new Bitmap(formBounds.Width, formBounds.Height );
using (Graphics g = Graphics.FromImage(bmp))
g.CopyFromScreen(formBounds.Location, Point.Empty, formBounds.Size);
//Clipboard.SetImage(bmp); for testing only
bmp.Dispose();
我可以随意切换用户,程序继续运行。
顺便说一句,您发布的链接真的很旧,很多事情可能已经改进了。
编辑:
有了更新的问题,事情就更清楚了。
因此,即使用户发生变化,您也希望不断获取程序的屏幕截图,对吗? 并且你想显示一个 WebControl,对吗?
用户可以拥有三种类型的桌面:登录/注销屏幕、屏幕保护程序屏幕和一个或多个普通桌面屏幕。但是,当用户注销时,他根本就没有桌面屏幕。
因此,如果用户没有活动桌面(g.CopyFromScreen
),则屏幕截图方法将不起作用,这将导致 GDI 错误,也不会使用网络上各种解决方案中的窗口句柄,包括您的链接造成。所有这些充其量只会显示空白或黑屏。
所以 DrawToBitmap 方法是唯一有效的方法。
您写道它有随机错误。这不是我看到的。
当用户以任何方式与WebBrowser
交互时,问题就会出现可预见。这包括滚动或单击导航或不导航。在这些交互之后,WebBrowser
将自己绘制为一个空框,直到它的 URL 被重新加载——不仅被刷新——而且真正被webBrowser1.Uri = new Uri(uriPath)
重新加载。这个是可以的,看我的other post
WebBrowser
在执行DrawToBitmap
时还有另一个问题:对于包含<input type="text"
元素的任何页面,它将失败(带有所述空框)。我不确定解决此问题的最佳方法是什么,更不用说为什么它首先发生了。屏幕截图方法没有这个特定问题。
编辑 2:
OP 挖掘出的代码通过调用PrintWindow
似乎解决了我们遇到的所有问题:它在注销时工作,即使在单击WebBrowser
后也可以使用 Refeshing 并刮掉所有页面,包括带有文本输入字段的页面。万岁!
在减少松弛之后,这里是一个可以创建Form
或只是WebBroser
(或任何其他Control
)的副本,带或不带边框:
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
public Bitmap CaptureWindow(Control ctl)
//Bitmap bmp = new Bitmap(ctl.Width, ctl.Height); // includes borders
Bitmap bmp = new Bitmap(ctl.ClientRectangle.Width, ctl.ClientRectangle.Height); // content only
using (Graphics graphics = Graphics.FromImage(bmp))
IntPtr hDC = graphics.GetHdc();
try PrintWindow(ctl.Handle, hDC, (uint)0);
finally graphics.ReleaseHdc(hDC);
return bmp;
【讨论】:
感谢您的回复,但是 DrawToBitmap 有一个错误,会随机返回空白图像。如果您可以发布一个在切换用户后执行屏幕截图的解决方案,我将非常感激,尽管我所有的尝试都返回了纯黑色图像。 查看我编辑的帖子!切换用户是否还涉及屏幕分辨率/颜色深度等的变化? 看到您的编辑。现在我们正在谈论。但我对我目前的结果感到困惑。你能描述一下随机错误吗? 请看看我自己的问题,关于问题here。一切正常,除非页面有文本输入字段.. 看来我找到了一些解决问题的代码……至少对我来说,请有空的时候看看。【参考方案2】:数字版权管理可能会阻止这种情况,因为 Windows 增加了对数字媒体的保护。
例如,如果您尝试使用 Microsoft 的图形渲染在 Media Player 或 Media Center 中创建某些内容的屏幕截图 - 是的,Microsoft 将“保护您免受任何潜在的诉讼”。
试试这个:单击键盘上的“打印屏幕”,然后进入 Microsoft Paint 并尝试将屏幕截图粘贴到其中。有什么吗?
【讨论】:
事实上,即使没有 DRM,一些视频覆盖卡(如电视卡)也可以做到这一点。但问题似乎来来去去,要么是随机的,要么是在切换用户时出现的。所以这两个原因都不应该适用。【参考方案3】:最后,即使我切换了用户,这段代码似乎也能正常工作。
对任何未保存的记事本进程进行屏幕截图的代码(“无标题 - 记事本”)
private void Form1_Load(object sender, EventArgs e)
//loop for debugging
int c = 0;
while(true)
c++;
System.Drawing.Bitmap image = CaptureWindow(FindWindow(null, "Untitled - Notepad"));
image.Save(".\\picture"+c.ToString()+".jpg");
Thread.Sleep(5000);
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public System.Drawing.Bitmap CaptureWindow(IntPtr hWnd)
System.Drawing.Rectangle rctForm = System.Drawing.Rectangle.Empty;
using (System.Drawing.Graphics grfx = System.Drawing.Graphics.FromHdc(GetWindowDC(hWnd)))
rctForm = System.Drawing.Rectangle.Round(grfx.VisibleClipBounds);
System.Drawing.Bitmap pImage = new System.Drawing.Bitmap(rctForm.Width, rctForm.Height);
System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(pImage);
IntPtr hDC = graphics.GetHdc();
try
PrintWindow(hWnd, hDC, (uint)0);
finally
graphics.ReleaseHdc(hDC);
return pImage;
请注意,窗口可能已隐藏,但仍必须在用户帐户上最大化才能获得完整的屏幕截图。
【讨论】:
虽然这适用于大多数 Windows,但我发现在 Windows 10 中,Microsoft Edge 仍然会产生黑色捕获。也许是因为 MS Edge 使用 DirectX 进行绘图? @GeoffCoope 不幸的是,我没有在 Windows 10 上测试过这种方法,但我认为这不是问题所在。此方法将截取整个句柄窗口,包括窗口框架,因此如果该方法返回黑色图像,则问题出在 c#。如果图像渲染正确,但只有浏览器控件显示为黑色,我猜这是 MS Edge 的问题。一定要进行更多的故障排除,并查看标记为解决方案的 TAWs 解决方案,它是我的更精简版本。如果您取得任何进展,请告诉我。 @Msegling 我认为 WPF 窗口会导致黑屏捕获。 Microsoft Edge 是一个 WPF 应用程序。我可以通过 HWND 捕获 Chrome 和 IE 11 并捕获窗口的网页区域(在其他窗口之后),但不能使用 Edge。到目前为止,我发现的唯一解决方案是强制 Edge 应用程序前进并使用“Windows.UI.Core.CoreWindow”类中的 rect 进行标准的屏幕复制捕获。这是在对 Spy++ 和 Inspect 进行大量修改之后。如果有人设法在后台窗口中捕获,请告诉我。【参考方案4】:我遇到了同样的问题,我无法使用 CopyFromScreen 方法,因为我的带有 WebBrowser 的表单可能被隐藏了。 PrintWindow 方法也不能很好地生成带有黑色区域的图像,尤其是当我的 MDI 表单部分被 MDI 父表单覆盖时。
最后,我在 SO 上使用了 this topic 中的 OleDraw 方法,但将它集成到从 WebBrowser 派生的类中。基本上,它只是覆盖了对 WM_PRINT 消息的标准控制反应。这不仅允许为 WebBrowser 执行正常的 Control.DrawToBitmap,还允许为其中包含 WebBrowser 的表单执行正常的 Control.DrawToBitmap。如果表单被隐藏(被另一个表单覆盖,包括 MDI 父表单),这也可以工作,并且当用户使用 Win+L 锁定会话时应该可以工作(我还没有测试过)。
public class WebBrowserEx : WebBrowser
private const uint DVASPECT_CONTENT = 1;
[DllImport("ole32.dll", PreserveSig = false)]
private static extern void OleDraw([MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwAspect,
IntPtr hdcDraw,
[In] ref System.Drawing.Rectangle lprcBounds
);
protected override void WndProc(ref Message m)
const int WM_PRINT = 0x0317;
switch (m.Msg)
case WM_PRINT:
Rectangle browserRect = new Rectangle(0, 0, this.Width, this.Height);
// Don't know why, but drawing with OleDraw directly on HDC from m.WParam.
// results in badly scaled (stretched) image of the browser.
// So, drawing to an intermediate bitmap first.
using (Bitmap browserBitmap = new Bitmap(browserRect.Width, browserRect.Height))
using (var graphics = Graphics.FromImage(browserBitmap))
var hdc = graphics.GetHdc();
OleDraw(this.ActiveXInstance, DVASPECT_CONTENT, hdc, ref browserRect);
graphics.ReleaseHdc(hdc);
using (var graphics = Graphics.FromHdc(m.WParam))
graphics.DrawImage(browserBitmap, Point.Empty);
// ignore default WndProc
return;
base.WndProc(ref m);
【讨论】:
以上是关于截图方法生成黑色图像的主要内容,如果未能解决你的问题,请参考以下文章