需要帮助将具有透明背景的图像设置到剪贴板中。

7
我需要帮助将一个透明图像设置到剪贴板中。我一直收到“句柄无效”的错误提示。基本上,我需要一个“第二个眼睛”来检查以下代码。(完整的工作项目在ftp://missico.net/ImageVisualizer.zip)。这是一个图像Debug Visualizer类库,但我制作了包含的项目以便于测试运行。(请注意,窗口是一个工具箱窗口,显示在任务栏中设置为false。)我厌倦了在工具箱窗口上执行屏幕截图,使用图像编辑器打开屏幕截图,然后删除添加的背景,因为它是一个屏幕截图。所以我想我会快速地将透明图像放到剪贴板上。问题是...Clipboard.SetImage没有透明度支持。Google来拯救...还不够。这是我目前的进展情况。我从许多来源获取。请参见代码的主要参考。我的问题是使用CF_DIBV5时出现“无效句柄”。我需要使用BITMAPV5HEADER和CreateDIBitmap吗?非常感谢你们GDI/GDI+巫师的任何帮助。
    public static void SetClipboardData(Bitmap bitmap, IntPtr hDC) {

        const uint SRCCOPY = 0x00CC0020;
        const int CF_DIBV5 = 17;
        const int CF_BITMAP = 2;

        //'reference
        //'http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/816a35f6-9530-442b-9647-e856602cc0e2

        IntPtr memDC = CreateCompatibleDC(hDC);
        IntPtr memBM = CreateCompatibleBitmap(hDC, bitmap.Width, bitmap.Height);

        SelectObject(memDC, memBM);

        using (Graphics g = Graphics.FromImage(bitmap)) {

            IntPtr hBitmapDC = g.GetHdc();
            IntPtr hBitmap = bitmap.GetHbitmap();

            SelectObject(hBitmapDC, hBitmap);

            BitBlt(memDC, 0, 0, bitmap.Width, bitmap.Height, hBitmapDC, 0, 0, SRCCOPY);

            if (!OpenClipboard(IntPtr.Zero)) {
                throw new System.Runtime.InteropServices.ExternalException("Could not open Clipboard", new Win32Exception());
            }

            if (!EmptyClipboard()) {
                throw new System.Runtime.InteropServices.ExternalException("Unable to empty Clipboard", new Win32Exception());
            }

            //IntPtr hClipboard = SetClipboardData(CF_BITMAP, memBM); //works but image is not transparent

            //all my attempts result in SetClipboardData returning hClipboard = IntPtr.Zero
            IntPtr hClipboard = SetClipboardData(CF_DIBV5, memBM);


            //because 
            if (hClipboard == IntPtr.Zero) {

                //    InnerException: System.ComponentModel.Win32Exception
                //         Message="The handle is invalid"
                //         ErrorCode=-2147467259
                //         NativeErrorCode=6
                //         InnerException: 

                throw new System.Runtime.InteropServices.ExternalException("Could not put data on Clipboard", new Win32Exception());
            }

            if (!CloseClipboard()) {
                throw new System.Runtime.InteropServices.ExternalException("Could not close Clipboard", new Win32Exception());
            }

            g.ReleaseHdc(hBitmapDC);

        }

    }

    private void __copyMenuItem_Click(object sender, EventArgs e) {

        using (Graphics g = __pictureBox.CreateGraphics()) {

            IntPtr hDC = g.GetHdc();

            MemoryStream ms = new MemoryStream();

            __pictureBox.Image.Save(ms, ImageFormat.Png);

            ms.Seek(0, SeekOrigin.Begin);

            Image imag = Image.FromStream(ms);

            // Derive BitMap object using Image instance, so that you can avoid the issue 
            //"a graphics object cannot be created from an image that has an indexed pixel format"

            Bitmap img = new Bitmap(new Bitmap(imag));

            SetClipboardData(img, hDC);

            g.ReleaseHdc();

        }

    }

你能澄清一下哪个步骤导致了无效句柄错误吗? - Adrian McCarthy
3个回答

5

有一些事情可以帮助您收紧代码库。我对CF_DIBV5做了一些功课,您可能会发现这些内容有帮助。

首先,在__copyMenuItem_Click()方法中,我们完全复制了您的图像四次,这远远超出了必要量。

  1. __pictureBox.Image
  2. Image imag = Image.FromStream(ms);
  3. new Bitmap(imag)
  4. Bitmap img = new Bitmap(new Bitmap(imag)); (外部位图)

此外,您的MemoryStreamimagnew Bitmap(imag)img没有被释放,这可能会导致问题。

在不改变代码意图(并可能不解决句柄问题)的情况下,您可以像这样重写该方法:

private void __copyMenuItem_Click(object sender, EventArgs e)
{
    var image =  __pictureBox.Image;
    using (var g = __pictureBox.CreateGraphics())
    using (var bmp = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb))
    using (var bmpg = Graphics.FromImage(bmp))
    {
        IntPtr hDC = g.GetHdc();
        bmpg.DrawImage(image, 0, 0, image.Width, image.Height);
        SetClipboardData(bmp, hDC);
        g.ReleaseHdc();
    }
}

接下来需要注意的是这一行代码:

IntPtr hClipboard = SetClipboardData(CF_DIBV5, memBM);

我很确定在使用CF_DIBV5时,您必须将BITMAPV5HEADER结构进行编组以向剪贴板传递位。我以前可能说错了,但我会验证memBM是否实际包含头文件。一个好的指标是第一个DWORD(UInt32)的值是否为124(头文件大小,以字节为单位)。
我的最后建议更多是推荐而非第二双眼睛的作用。我发现像GIMP、Paint.NET、Fireworks和PhotoScape这样的照片应用程序似乎对CF_DIBV5(Format17)的粘贴支持不佳或不存在。您可以考虑将PNG格式复制到剪贴板上,并备份不透明位图以防目标应用程序不支持PNG。我使用扩展方法来简化此过程:
public static void CopyMultiFormatBitmapToClipboard(this Image image)
{
    using (var opaque = image.CreateOpaqueBitmap(Color.White))
    using (var stream = new MemoryStream())
    {
        image.Save(stream, ImageFormat.Png);

        Clipboard.Clear();
        var data = new DataObject();
        data.SetData(DataFormats.Bitmap, true, opaque);
        data.SetData("PNG", true, stream);
        Clipboard.SetDataObject(data, true);
    }
}

使用扩展方法,您的 __copyMenuItem_Click() 方法可以简化为以下内容,并且可以完全删除 SetClipboardData() 方法:
private void __copyMenuItem_Click(object sender, EventArgs e)
{
    __pictureBox.Image.CopyMultiFormatBitmapToClipboard();
}

现在,正如我们在另一个帖子中讨论的那样,PNG支持可能不适合您。我已经在一些应用程序上测试了这种方法;然而,您需要确定这是否足够满足您对透明度的要求。
以下是一些应用程序的支持情况:
- GIMP:支持透明度 - Fireworks(3.0):支持透明度 - PhotoScape:白色背景 - Paint.NET:白色背景 - MS PowerPoint 2007:支持透明度 - MS Word 2007:白色背景 - MS Paint(Win7):白色背景
我调查的所有内容讨论起来太长了,无法在Stack Overflow上进行。我在我的博客上提供了额外的示例代码和讨论:http://www.notesoncode.com/articles/2010/08/16/HandlingTransparentImagesOnTheClipboardIsForSomeReasonHard.aspx
祝你好运!

我没有时间尝试你的代码,但是我会给你奖励,以表彰你的辛勤工作。发布的代码是一次性的代码,我从许多来源收集而来,只为了让它能够运行。因此,你的“第二双眼睛”和建议是非常有帮助的。一旦我赶上进度,我会尝试你的建议。 - AMissico
我发现很多应用滥用了32位v1 DIB(40字节标头),类型为“ bitfields”(压缩= 3),将其作为ARGB,而根据其规格,它只是RGB。其中包括Windows 10的屏幕截图(将“ alpha”位设置为255,除了我的双显示器设置中不存在的部分)和Google Chrome。 - Nyerguds

1

我看到三个问题:

  1. 无效的句柄错误可能来自于将 memBM 保留在 memDC 中。在将其传递给其他地方之前,您应该始终从 DC 中选择位图。

  2. BitBlt 是 GDI 调用(而不是 GDI+)。GDI 不会保留 alpha 通道。在较新版本的 Windows 上,您可以使用 AlphaBlend 将带有 alpha 的位图合成到背景上,但合成物将没有 alpha 通道。

  3. 您已经创建了一个兼容位图,这意味着位图具有与您传递的 DC 相同的颜色格式(我假设与屏幕相同)。因此,您的位图可能会以 16 位、24 位、32 位甚至 8 位的形式出现,具体取决于屏幕的设置。如果 BitBlt 保留了原始 alpha 通道,则在转换为屏幕格式时可能会丢失它。


1
AlphaBlend 已经存在相当长的时间了。 - Chris O

0

Bitmap.GetHbitmap() 方法会将位图合成到不透明的背景上,从而丢失 alpha 通道。这个问题 解决了如何获取保留 alpha 通道的 HBITMAP。


相同的问题是“无效句柄”。此外,如果我使用CF_BITMAP,则背景是黑色,而不是PictureBox控件使用的“控件灰色”。 - AMissico
剪贴板格式的文档(参见http://msdn.microsoft.com/en-us/library/ms649013.aspx)指出,当使用CF_DIB时,句柄应为使用GlobalAlloc分配的缓冲区,其中包含BITMAPINFO结构,后跟位图位。我希望您需要手动编组此内容。当使用CF_BITMAP时,您可以传递HBITMAP,但没有相关的元数据来指定alpha通道的使用,因此它将被忽略。 - Chris Ostler

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