C# 从剪贴板复制和复制到剪贴板会丢失图像透明度

C# 从剪贴板复制和复制到剪贴板会丢失图像透明度,c#,wpf,bitmap,transparency,clipboard,C#,Wpf,Bitmap,Transparency,Clipboard,我一直在尝试将一个透明的PNG图像复制到剪贴板,并保持其透明度将其粘贴到支持它的特定程序中 我已经尝试了很多解决方案,但背景总是以某种方式变成灰色 所以我试着用Chrome复制同一张图片,然后把它粘贴到程序中,结果成功了。它保持了透明度。然后我试着从我用Chrome复制的剪贴板中获取图像,并再次设置图像,期望透明度仍然存在-但是没有,即使我只是从剪贴板中获取图像并再次设置,透明度也没有保留 var img = Clipboard.GetImage(); // copied using Chrom

我一直在尝试将一个透明的PNG图像复制到剪贴板,并保持其透明度将其粘贴到支持它的特定程序中

我已经尝试了很多解决方案,但背景总是以某种方式变成灰色

所以我试着用Chrome复制同一张图片,然后把它粘贴到程序中,结果成功了。它保持了透明度。然后我试着从我用Chrome复制的剪贴板中获取图像,并再次设置图像,期望透明度仍然存在-但是没有,即使我只是从剪贴板中获取图像并再次设置,透明度也没有保留

var img = Clipboard.GetImage(); // copied using Chrome and transparency is preserved
Clipboard.SetImage(img); // transparency lost

即使我使用
System.Windows.Forms.Clipboard
或尝试获取和设置
DataObject
而不是图像,也会出现同样的问题。

默认情况下,Windows剪贴板不支持透明度,但是,您可以将剪贴板上的内容以多种类型放在一起,以确保大多数应用程序在其中找到可以使用的类型。不幸的是,最常见的类型,
DeviceIndependentBitmap
(Windows本身似乎使用的)是一种非常肮脏和不可靠的类型。我写了一篇长篇大论的解释

在继续我的回答之前,我假设您已经通读了,因为它包含了下一部分所需的背景信息

现在,最干净的方法是使用PNG流将图像粘贴到剪贴板上,但不能保证所有应用程序都可以粘贴它。Gimp支持PNG粘贴,较新的MS Office程序显然也支持PNG粘贴,但谷歌Chrome就不支持,它只接受我链接到的答案中详细说明的杂乱无章的DIB类型。另一方面,Gimp不会接受DIB具有透明度,因为它的创建者实际上遵循了格式的规范,并且意识到格式不可靠(正如我链接的那个问题清楚地表明的那样)

遗憾的是,由于DIB的混乱,最好的办法就是将它放在尽可能多的受支持的类型中,包括PNG、DIB和普通图像

PNG和DIB都以相同的方式放在剪贴板上:将它们作为
MemoryStream
放在
DataObject
中,然后在实际放在剪贴板上时给剪贴板“copy”指令

这其中大部分都很简单,但DIB则要复杂一些。请注意,以下部分包含对我自己的工具集的一些引用。可以找到
GetImageData
一个,可以找到
BuildImage
一个,下面给出了
ArrayUtils
一个

但是,这些工具集都使用
System.Drawing
。你必须自己弄清楚如何在WPF中做同样的事情

/// <summary>
/// Copies the given image to the clipboard as PNG, DIB and standard Bitmap format.
/// </summary>
/// <param name="image">Image to put on the clipboard.</param>
/// <param name="imageNoTr">Optional specifically nontransparent version of the image to put on the clipboard.</param>
/// <param name="data">Clipboard data object to put the image into. Might already contain other stuff. Leave null to create a new one.</param>
public static void SetClipboardImage(Bitmap image, Bitmap imageNoTr, DataObject data)
{
    Clipboard.Clear();
    if (data == null)
        data = new DataObject();
    if (imageNoTr == null)
        imageNoTr = image;
    using (MemoryStream pngMemStream = new MemoryStream())
    using (MemoryStream dibMemStream = new MemoryStream())
    {
        // As standard bitmap, without transparency support
        data.SetData(DataFormats.Bitmap, true, imageNoTr);
        // As PNG. Gimp will prefer this over the other two.
        image.Save(pngMemStream, ImageFormat.Png);
        data.SetData("PNG", false, pngMemStream);
        // As DIB. This is (wrongly) accepted as ARGB by many applications.
        Byte[] dibData = ConvertToDib(image);
        dibMemStream.Write(dibData, 0, dibData.Length);
        data.SetData(DataFormats.Dib, false, dibMemStream);
        // The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation.
        Clipboard.SetDataObject(data, true);
    }
}
    
/// <summary>
/// Converts the image to Device Independent Bitmap format of type BITFIELDS.
/// This is (wrongly) accepted by many applications as containing transparency,
/// so I'm abusing it for that.
/// </summary>
/// <param name="image">Image to convert to DIB</param>
/// <returns>The image converted to DIB, in bytes.</returns>
public static Byte[] ConvertToDib(Image image)
{
    Byte[] bm32bData;
    Int32 width = image.Width;
    Int32 height = image.Height;
    // Ensure image is 32bppARGB by painting it on a new 32bppARGB image.
    using (Bitmap bm32b = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb))
    {
        using (Graphics gr = Graphics.FromImage(bm32b))
            gr.DrawImage(image, new Rectangle(0, 0, bm32b.Width, bm32b.Height));
        // Bitmap format has its lines reversed.
        bm32b.RotateFlip(RotateFlipType.Rotate180FlipX);
        Int32 stride;
        bm32bData = ImageUtils.GetImageData(bm32b, out stride);
    }
    // BITMAPINFOHEADER struct for DIB.
    Int32 hdrSize = 0x28;
    Byte[] fullImage = new Byte[hdrSize + 12 + bm32bData.Length];
    //Int32 biSize;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x00, 4, true, (UInt32)hdrSize);
    //Int32 biWidth;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x04, 4, true, (UInt32)width);
    //Int32 biHeight;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x08, 4, true, (UInt32)height);
    //Int16 biPlanes;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
    //Int16 biBitCount;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
    //BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
    //Int32 biSizeImage;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x14, 4, true, (UInt32)bm32bData.Length);
    // These are all 0. Since .net clears new arrays, don't bother writing them.
    //Int32 biXPelsPerMeter = 0;
    //Int32 biYPelsPerMeter = 0;
    //Int32 biClrUsed = 0;
    //Int32 biClrImportant = 0;

    // The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
    ArrayUtils.WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
    ArrayUtils.WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
    ArrayUtils.WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
    Array.Copy(bm32bData, 0, fullImage, hdrSize + 12, bm32bData.Length);
    return fullImage;
}
这里使用的
CloneImage
函数基本上就是我的
GetImageData
BuildImage
工具集的组合,确保创建新图像时不会有任何可能出错的备份资源;当图像对象基于一个
,然后被释放时,它们会导致崩溃。发布了一个经过压缩和优化的版本


谢谢你的详细回答!我会尽可能快地试用它,并向您汇报。我刚刚发现,实际上支持alpha的版本5DIB也可以得到支持。它有剪贴板ID“Format17”,因为。嗨。这是正确的克隆图像函数吗:静态位图克隆图像(位图bm){int-stride;返回BuildImage(GetImageData(bm,out-stride),bm.Width,bm.Height,stride,bm.PixelFormat,null,null);}@ValeryLetroye或多或少,但是如果您想在其中也支持调色板图像,您需要使用bm.palete填充BuildImage的palete参数。它不会给出高颜色图像的例外情况;它只会返回一个0条目调色板,BuildImage会忽略非索引图像上的参数。
DataObject retrievedData = Clipboard.GetDataObject() as DataObject;
/// <summary>
/// Retrieves an image from the given clipboard data object, in the order PNG, DIB, Bitmap, Image object.
/// </summary>
/// <param name="retrievedData">The clipboard data.</param>
/// <returns>The extracted image, or null if no supported image type was found.</returns>
public static Bitmap GetClipboardImage(DataObject retrievedData)
{
    Bitmap clipboardimage = null;
    // Order: try PNG, move on to try 32-bit ARGB DIB, then try the normal Bitmap and Image types.
    if (retrievedData.GetDataPresent("PNG"))
    {
        MemoryStream png_stream = retrievedData.GetData("PNG") as MemoryStream;
        if (png_stream != null)
            using (Bitmap bm = new Bitmap(png_stream))
                clipboardimage = ImageUtils.CloneImage(bm);
    }
    if (clipboardimage == null && retrievedData.GetDataPresent(DataFormats.Dib))
    {
        MemoryStream dib = retrievedData.GetData(DataFormats.Dib) as MemoryStream;
        if (dib != null)
            clipboardimage = ImageFromClipboardDib(dib.ToArray());
    }
    if (clipboardimage == null && retrievedData.GetDataPresent(DataFormats.Bitmap))
        clipboardimage = new Bitmap(retrievedData.GetData(DataFormats.Bitmap) as Image);
    if (clipboardimage == null && retrievedData.GetDataPresent(typeof(Image)))
        clipboardimage = new Bitmap(retrievedData.GetData(typeof(Image)) as Image);
    return clipboardimage;
}

public static Bitmap ImageFromClipboardDib(Byte[] dibBytes)
{
    if (dibBytes == null || dibBytes.Length < 4)
        return null;
    try
    {
        Int32 headerSize = (Int32)ArrayUtils.ReadIntFromByteArray(dibBytes, 0, 4, true);
        // Only supporting 40-byte DIB from clipboard
        if (headerSize != 40)
            return null;
        Byte[] header = new Byte[40];
        Array.Copy(dibBytes, header, 40);
        Int32 imageIndex = headerSize;
        Int32 width = (Int32)ArrayUtils.ReadIntFromByteArray(header, 0x04, 4, true);
        Int32 height = (Int32)ArrayUtils.ReadIntFromByteArray(header, 0x08, 4, true);
        Int16 planes = (Int16)ArrayUtils.ReadIntFromByteArray(header, 0x0C, 2, true);
        Int16 bitCount = (Int16)ArrayUtils.ReadIntFromByteArray(header, 0x0E, 2, true);
        //Compression: 0 = RGB; 3 = BITFIELDS.
        Int32 compression = (Int32)ArrayUtils.ReadIntFromByteArray(header, 0x10, 4, true);
        // Not dealing with non-standard formats.
        if (planes != 1 || (compression != 0 && compression != 3))
            return null;
        PixelFormat fmt;
        switch (bitCount)
        {
            case 32:
                fmt = PixelFormat.Format32bppRgb;
                break;
            case 24:
                fmt = PixelFormat.Format24bppRgb;
                break;
            case 16:
                fmt = PixelFormat.Format16bppRgb555;
                break;
            default:
                return null;
        }
        if (compression == 3)
            imageIndex += 12;
        if (dibBytes.Length < imageIndex)
            return null;
        Byte[] image = new Byte[dibBytes.Length - imageIndex];
        Array.Copy(dibBytes, imageIndex, image, 0, image.Length);
        // Classic stride: fit within blocks of 4 bytes.
        Int32 stride = (((((bitCount * width) + 7) / 8) + 3) / 4) * 4;
        if (compression == 3)
        {
            UInt32 redMask = ArrayUtils.ReadIntFromByteArray(dibBytes, headerSize + 0, 4, true);
            UInt32 greenMask = ArrayUtils.ReadIntFromByteArray(dibBytes, headerSize + 4, 4, true);
            UInt32 blueMask = ArrayUtils.ReadIntFromByteArray(dibBytes, headerSize + 8, 4, true);
            // Fix for the undocumented use of 32bppARGB disguised as BITFIELDS. Despite lacking an alpha bit field,
            // the alpha bytes are still filled in, without any header indication of alpha usage.
            // Pure 32-bit RGB: check if a switch to ARGB can be made by checking for non-zero alpha.
            // Admitted, this may give a mess if the alpha bits simply aren't cleared, but why the hell wouldn't it use 24bpp then?
            if (bitCount == 32 && redMask == 0xFF0000 && greenMask == 0x00FF00 && blueMask == 0x0000FF)
            {
                // Stride is always a multiple of 4; no need to take it into account for 32bpp.
                for (Int32 pix = 3; pix < image.Length; pix += 4)
                {
                    // 0 can mean transparent, but can also mean the alpha isn't filled in, so only check for non-zero alpha,
                    // which would indicate there is actual data in the alpha bytes.
                    if (image[pix] == 0)
                        continue;
                    fmt = PixelFormat.Format32bppPArgb;
                    break;
                }
            }
            else
                // Could be supported with a system that parses the colour masks,
                // but I don't think the clipboard ever uses these anyway.
                return null;
        }
        Bitmap bitmap = ImageUtils.BuildImage(image, width, height, stride, fmt, null, null);
        // This is bmp; reverse image lines.
        bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
        return bitmap;
    }
    catch
    {
        return null;
    }
}
public static void WriteIntToByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian, UInt32 value)
{
    Int32 lastByte = bytes - 1;
    if (data.Length < startIndex + bytes)
        throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + ".");
    for (Int32 index = 0; index < bytes; index++)
    {
        Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
        data[offs] = (Byte)(value >> (8 * index) & 0xFF);
    }
}

public static UInt32 ReadIntFromByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian)
{
    Int32 lastByte = bytes - 1;
    if (data.Length < startIndex + bytes)
        throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to read a " + bytes + "-byte value at offset " + startIndex + ".");
    UInt32 value = 0;
    for (Int32 index = 0; index < bytes; index++)
    {
        Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
        value += (UInt32)(data[offs] << (8 * index));
    }
    return value;
}