1

我正在尝试将图像文件加载到剪贴板,然后使用 Ctrl+V 将其粘贴到 Discord 中。可以将 PNG 粘贴到 Discord 并保留透明度,因为我可以右键单击以在 Mozilla 或 Chrome 上复制图像,然后按 Ctrl+V 将其粘贴到 Discord 客户端。查看剪贴板内容显示 Mozilla在复制图像后存储DeviceIndependantBitmap和数据格式。Format17因此,我调整了从剪贴板复制到剪贴板失去图像透明度的帖子中的功能,以将Image对象转换为DIBv5数据并将其加载到DeviceIndependantFormat名称下的剪贴板中。

但是不支持透明度。然后将图像粘贴到 Discord 中时,所有透明区域都用黑色填充。这是用于测试在剪贴板中加载 PNG 的脚本。复制图像时,DIB 标头与 Mozilla 的 DIB 标头完全匹配(仅更改了宽度、高度和像素数信息):

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;

namespace ClipboardDIBTest
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            // Loads a PNG with transparency in the clipboard.
            SetClipboardImage(Image.FromFile("./test.png"));
        }

        /// <summary>Loads an image into the DIBv5 format in the clipboard,
        /// under the `DeviceIndependantBitmap` format.</summary>
        static void SetClipboardImage(Image image)
        {
            Clipboard.Clear();
            DataObject data = new DataObject();
            using (MemoryStream dibV5MemStream = new MemoryStream())
            {
                Byte[] dibV5Data = ConvertToDIBv5(image);
                dibV5MemStream.Write(dibV5Data, 0, dibV5Data.Length);
                data.SetData(DataFormats.Dib, false, dibV5MemStream);
                // The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation.
                Clipboard.SetDataObject(data, true);
            }
        }
        
        /// <summary>Retrieves bitmap data from the image, ensuring an ARGB pixel format.</summary>
        static Byte[] GetBM32Data(Image image)
        {
            Byte[] bm32bData;
            // 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);
                bm32bData = GetImageData(bm32b);
            }

            return bm32bData;
        }

        /// <summar>Retrieves the pixel data from a bitmap.</summary>
        static byte[] GetImageData(Bitmap bmp)
        {
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);
            IntPtr ptr = bmpData.Scan0;
            int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
            byte[] data = new byte[bytes];
            Marshal.Copy(ptr, data, 0, bytes);
            bmp.UnlockBits(bmpData);
            return data;
        }

        /// <summary>Converts the image into a DIBv5 (Format17) data format.</summary>
        static Byte[] ConvertToDIBv5(Image image)
        {
            Byte[] bm32bData = GetBM32Data(image);
            Int32 width = image.Width;
            Int32 height = image.Height;

            // Header data built to match Mozilla's Format17 content when an image is copied.

            Int32 hdrSize = 124;
            Byte[] fullImage = new Byte[hdrSize + bm32bData.Length];
            //Int32 bV5Size;
            WriteIntToByteArray(fullImage, 0, 4, true, (UInt32)hdrSize);
            //Int32 bV5Width;
            WriteIntToByteArray(fullImage, 4, 4, true, (UInt32)width);
            //Int32 bV5Height;
            WriteIntToByteArray(fullImage, 8, 4, true, (UInt32)height);
            //Int16 bV5Planes;
            WriteIntToByteArray(fullImage, 12, 2, true, 1);
            //Int16 bV5BitCount;
            WriteIntToByteArray(fullImage, 14, 2, true, 32);
            //Int32 bV5Compression;
            WriteIntToByteArray(fullImage, 16, 4, true, 0);
            //Int32 biSizeImage;
            WriteIntToByteArray(fullImage, 20, 4, true, (UInt32)bm32bData.Length);
            // These are all 0. Since .net clears new arrays, don't bother writing them.
            //Int32 bV5XPelsPerMeter = 0;
            //Int32 bV5YPelsPerMeter = 0;
            //Int32 bV5ClrUsed = 0;
            //Int32 bV5ClrImportant = 0;
            //Int32 Red/Green/Blue/Alpha masks
            WriteIntToByteArray(fullImage, 40, 4, true, 0x000000FF);
            WriteIntToByteArray(fullImage, 44, 4, true, 0x0000FF00);
            WriteIntToByteArray(fullImage, 48, 4, true, 0x00FF0000);
            WriteIntToByteArray(fullImage, 52, 4, true, 0xFF000000);
            // Int32 bV5CSType : "sRGB"
            WriteIntToByteArray(fullImage, 56, 4, true, 1934772034);
            // Rest is all 0

            Array.Copy(bm32bData, 0, fullImage, hdrSize, bm32bData.Length);
            return fullImage;
        }

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

        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;
        }
    }
}
  • 原始图像文件:

原始PNG图像,具有透明度。

  • 运行程序后,使用 Ctrl+V 在 Discord 中粘贴的图像:

将结果粘贴到 Discord 中。 透明区域用黑色填充。

我在调试/逆向工程尝试期间尝试了以下操作:

  • 从Mozilla复制图像(右键单击并复制),然后读取该Format17名称下存储在剪贴板中的数据,并将其保存到文件中,然后再次将文件内容加载到剪贴板中。如果以名称加载,则Format17Ctrl+V 不会发生任何事情(Discord 不会检测到任何要粘贴的内容)。但是,如果在下面加载,DeviceIndependantBitmap则图像会正确粘贴到 Discord 中(具有透明度)。
  • 使用上述帖子中的相同代码以更简单的 DIB 格式加载图片,尽管它也提供黑色背景。

我读过的其他帖子:

有谁知道为什么会发生这种情况以及如何解决它?谢谢。

4

0 回答 0