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

AMi*_*ico 6 clipboard transparency gdi+ gdi winforms

我需要帮助设置透明图像到剪贴板.我一直得到"处理无效".基本上,我需要一个"第二组眼睛"来查看以下代码.(ftp://missico.net/ImageVisualizer.zip上的完整工作项目.)

这是一个图像Debug Visualizer类库,但我使包含的项目作为可执行文件运行进行测试.(请注意,窗口是工具箱窗口,任务栏中的显示设置为false.)我厌倦了必须在工具箱窗口上执行屏幕捕获,使用图像编辑器打开屏幕捕获,然后删除添加的背景,因为它是一个屏幕截图.所以我想我会很快将透明图像放到剪贴板上.好吧,问题是......没有对Clipboard.SetImage的透明度支持.谷歌救援......并不完全.

这就是我到目前为止所拥有的.我从众多消息来源中撤出.请参阅主要参考的代码.我的问题是使用CF_DIBV5时的"无效句柄".我需要使用BITMAPV5HEADER和CreateDIBitmap吗?

GDI/GDI + Wizards的任何帮助将不胜感激.

    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();

        }

    }
Run Code Online (Sandbox Code Playgroud)

kbr*_*ton 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来看起来需要注意的是这行:

IntPtr hClipboard = SetClipboardData(CF_DIBV5, memBM);
Run Code Online (Sandbox Code Playgroud)

我相当确定,在使用 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

有了扩展方法,您的__copyMenuItem_Click()方法可以简化为以下内容,并且SetClipboardData()可以完全删除该方法:

private void __copyMenuItem_Click(object sender, EventArgs e)
{
    __pictureBox.Image.CopyMultiFormatBitmapToClipboard();
}
Run Code Online (Sandbox Code Playgroud)

现在,正如我们已经在另一个线程中讨论的那样,PNG 支持可能不适合您。我已经在一些应用程序上测试了这种方法;但是,这将由您决定这是否足以满足您的要求的透明度支持。

  • GIMP:支持透明度
  • Fireworks (3.0):支持透明度
  • 照片景观:白色背景
  • Paint.NET:白色背景
  • MS PowerPoint 2007:支持透明度
  • MS Word 2007:白色背景
  • MS Paint (Win7):白色背景

对于 Stack Overflow 来说,我所研究的所有内容的讨论都太长了。我的博客上有其他示例代码和讨论:http://www.notesoncode.com/articles/2010/08/16/HandlingTransparentImagesOnTheClipboardIsForSomeReasonHard.aspx

祝你好运!


Chr*_*ler 0

Bitmap.GetHbitmap()实际上会将位图​​合成到不透明背景,丢失 Alpha 通道。 这个问题解决了如何获得 alpha 通道完好无损的 HBITMAP。