我正在尝试将图像文件加载到剪贴板,然后使用 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;
}
}
}
- 原始图像文件:
- 运行程序后,使用 Ctrl+V 在 Discord 中粘贴的图像:
我在调试/逆向工程尝试期间尝试了以下操作:
- 从Mozilla复制图像(右键单击并复制),然后读取该
Format17
名称下存储在剪贴板中的数据,并将其保存到文件中,然后再次将文件内容加载到剪贴板中。如果以名称加载,则Format17
Ctrl+V 不会发生任何事情(Discord 不会检测到任何要粘贴的内容)。但是,如果在下面加载,DeviceIndependantBitmap
则图像会正确粘贴到 Discord 中(具有透明度)。 - 使用上述帖子中的相同代码以更简单的 DIB 格式加载图片,尽管它也提供黑色背景。
我读过的其他帖子:
- 如何将 CF_DIBV5 从剪贴板 (Format17) 转换为透明位图?
- 如何将剪贴板中的透明图像粘贴到 C# winforms 应用程序中?
- 如何仅使用直接 WinAPI 将与设备无关的位图放入 Windows 剪贴板?(没有 MFC 或其他包装器)
- Win32 剪贴板和 Alpha 通道图像
有谁知道为什么会发生这种情况以及如何解决它?谢谢。