从剪贴板复制到剪贴板会失去图像透明度

从剪贴板复制到剪贴板会失去图像透明度,第1张

剪贴复制到剪贴板会失去图像透明度

Windows剪贴板默认情况下不支持透明,但是您可以将剪贴板上的内容以多种类型放在一起,以确保大多数应用程序在其中找到可以使用的某种类型。可悲的是,最常见的类型(Windows本身似乎在使用)是一种非常肮脏且不可靠的类型。我写了一个很大的
咆哮 有关解释这里。

我假设您已经阅读了该内容,然后再继续此处的答案,因为它包含了下一部分所需的背景信息。

现在,将图像放在剪贴板上并提供透明性支持的最简单方法是PNG流,但它不能保证所有应用程序都可以粘贴它。Gimp支持PNG粘贴,而较新的MS
Office程序显然也支持PNG粘贴,但例如Google
Chrome不支持PNG粘贴,并且将仅接受我链接的答案中详述的凌乱的DIB类型。另一方面,Gimp不会接受DIB具有透明度,因为它的创建者实际上遵循了格式的规范,并且意识到格式不可靠(我链接的那个问题清楚地表明了这一点)。

不幸的是,由于DIB混乱,最好的办法就是将其放入尽可能多的受普遍支持的类型中,包括PNG,DIB和常规Image。

PNG和DIB都以相同的方式放置在剪贴板上:将它们放在

DataObject
as中
MemoryStream
,然后在实际放置时为剪贴板提供“复制”指令。

其中大多数都很简单,但是DIB有点复杂。请注意,以下部分包含一些对我自己的工具集的引用。在

GetImageData
一个可以发现这个答案的
BuildImage
人可以发现,在这里,和
ArrayUtils
那些如下。

/// <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;}

现在,关于从剪贴板上获取图像,我注意到.Net
3.5与更高版本之间的行为显然存在差异,后者似乎实际上使用了该DIB。考虑到这种差异,并考虑到DIB格式的可靠性,您将需要手动检查所有类型,最好从完全可靠的PNG格式开始。

您可以

DataObject
使用以下代码从剪贴板中获取:

DataObject retrievedData = Clipboard.GetDataObject() as DataObject;

CloneImage
这里使用的功能基本上只是我
GetImageData
BuildImage
工具集的组合,从而确保创建新映像而没有任何可能弄乱的后备资源;已知图像对象基于时
Stream
会被丢弃,从而导致崩溃。它的压缩和优化版本已发布在此处,这个问题值得一读,以了解为什么克隆如此重要。

/// <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;    }}

因为

BitConverter
总是需要对系统字节序是愚蠢的检查,我有我自己的
ReadIntFromByteArray
,并
WriteIntToByteArray
ArrayUtils
类:

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;}


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/zaji/5567384.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-12-14
下一篇2022-12-14

发表评论

登录后才能评论

评论列表(0条)

    保存