如何解决即使使用 DIBv5 转换,C# 图像在剪贴板中加载时也会失去透明度
我正在尝试将图像文件加载到剪贴板中,然后使用 Ctrl+V 将其粘贴到 Discord 中。可以将 PNG 粘贴到 Discord 并保留透明度,因为我可以右键单击以在 Mozilla 或 Chrome 上复制图像,然后按 Ctrl+V 将其粘贴到 Discord 客户端中。查看剪贴板内容显示 Mozilla 在复制图像后存储 DeviceIndependantBitmap
和 Format17
数据格式。因此,我调整了帖子 Copying From and To Clipboard loses image transparency 中的函数,将 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,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,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,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,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,4,true,(UInt32)hdrSize);
//Int32 bV5Width;
WriteIntToByteArray(fullImage,(UInt32)width);
//Int32 bV5Height;
WriteIntToByteArray(fullImage,8,(UInt32)height);
//Int16 bV5Planes;
WriteIntToByteArray(fullImage,12,2,1);
//Int16 bV5BitCount;
WriteIntToByteArray(fullImage,14,32);
//Int32 bV5Compression;
WriteIntToByteArray(fullImage,16,0);
//Int32 biSizeImage;
WriteIntToByteArray(fullImage,20,(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,0x000000FF);
WriteIntToByteArray(fullImage,44,0x0000FF00);
WriteIntToByteArray(fullImage,48,0x00FF0000);
WriteIntToByteArray(fullImage,52,0xFF000000);
// Int32 bV5CSType : "sRGB"
WriteIntToByteArray(fullImage,56,1934772034);
// Rest is all 0
Array.Copy(bm32bData,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,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 中(透明)。 - 使用上面提到的 post 中的相同代码以更简单的 DIB 格式加载图片,尽管它也提供了黑色背景。
我读过的其他帖子:
- How can I convert a CF_DIBV5 from Clipboard (Format17) to a Transparent Bitmap?
- How to paste a transparent image from the clipboard in a C# winforms app?
- How can I put a device-independent bitmap into the Windows clipboard using just direct WinAPI? (No MFC or other wrappers)
- Win32 clipboard and alpha channel images
有人知道为什么会发生这种情况以及如何解决吗?谢谢。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。