I'm trying to load an image file into the clipboard in order to then paste it in Discord with Ctrl+V. It is possible to paste a PNG into Discord and conserve the transparency, as I am able to right click to copy an image on Mozilla or Chrome and then press Ctrl+V to paste it into the Discord client. A look to the clipboard content shows that Mozilla stores DeviceIndependantBitmap
and Format17
data formats after copying an image. I therefore adapted the functions from the post Copying From and To Clipboard loses image transparency to convert an Image
object into DIBv5
data and load it in the clipboard under the DeviceIndependantFormat
name.
However the transparency is not supported. When then pasting the image into Discord, all transparent regions are filled with a black color. Here is the script used to test the loading of a PNG in the clipboard. The DIB header is built to exactly match Mozilla's DIB header when an image is copied (with only width, height and number of pixels information changed):
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;
}
}
}
I tried the following during my debugging/retroengineering attempts:
Format17
name, and saving it into a file before loading the content of the file into the clipboard again. If loaded under the Format17
name nothing happens on Ctrl+V (Discord doesn't detect anything to paste). But if loaded under DeviceIndependantBitmap
then the image is correctly pasted in Discord (with transparency).Other posts I've read:
Does anyone have an idea on why this happens and how to fix it? Thanks.
User contributions licensed under CC BY-SA 3.0