Win32 - export bitmap in more clipboard formats (#20209)
* win32 - export bitmap in more clipboard formats * addressed review * use compatible bitmap for ole gdi copy * change bitmap intent to colormetric --------- Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Utilities;
|
||||
using Avalonia.Win32.Interop;
|
||||
|
||||
@@ -12,17 +13,28 @@ namespace Avalonia.Win32
|
||||
private const int MaxFormatNameLength = 260;
|
||||
private const string AppPrefix = "avn-app-fmt:";
|
||||
public const string PngFormatMimeType = "image/png";
|
||||
public const string JpegFormatMimeType = "image/jpeg";
|
||||
public const string PngFormatSystemType = "PNG";
|
||||
public const string BitmapFormat = "CF_BITMAP";
|
||||
public const string DibFormat = "CF_DIB";
|
||||
public const string DibV5Format = "CF_DIBV5";
|
||||
private static readonly List<(DataFormat Format, ushort Id)> s_formats = [];
|
||||
|
||||
public static DataFormat PngSystemDataFormat = DataFormat.FromSystemName<Bitmap>(PngFormatSystemType, AppPrefix);
|
||||
public static DataFormat PngMimeDataFormat = DataFormat.FromSystemName<Bitmap>(PngFormatMimeType, AppPrefix);
|
||||
public static DataFormat HBitmapDataFormat = DataFormat.FromSystemName<Bitmap>(BitmapFormat, AppPrefix);
|
||||
public static DataFormat DibDataFormat = DataFormat.FromSystemName<Bitmap>(DibFormat, AppPrefix);
|
||||
public static DataFormat DibV5DataFormat = DataFormat.FromSystemName<Bitmap>(DibV5Format, AppPrefix);
|
||||
|
||||
public static DataFormat[] ImageFormats = [PngMimeDataFormat, PngSystemDataFormat, DibDataFormat, DibV5DataFormat, HBitmapDataFormat];
|
||||
|
||||
static ClipboardFormatRegistry()
|
||||
{
|
||||
AddDataFormat(DataFormat.Text, (ushort)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT);
|
||||
AddDataFormat(DataFormat.Text, (ushort)UnmanagedMethods.ClipboardFormat.CF_TEXT);
|
||||
AddDataFormat(DataFormat.File, (ushort)UnmanagedMethods.ClipboardFormat.CF_HDROP);
|
||||
AddDataFormat(DataFormat.Bitmap, (ushort)UnmanagedMethods.ClipboardFormat.CF_DIB);
|
||||
AddDataFormat(DibDataFormat, (ushort)UnmanagedMethods.ClipboardFormat.CF_DIB);
|
||||
AddDataFormat(DibV5DataFormat, (ushort)UnmanagedMethods.ClipboardFormat.CF_DIBV5);
|
||||
AddDataFormat(HBitmapDataFormat, (ushort)UnmanagedMethods.ClipboardFormat.CF_BITMAP);
|
||||
}
|
||||
|
||||
private static void AddDataFormat(DataFormat format, ushort id)
|
||||
@@ -62,24 +74,18 @@ namespace Avalonia.Win32
|
||||
if (DataFormat.Bitmap.Equals(format))
|
||||
{
|
||||
(DataFormat, ushort)? pngFormat = null;
|
||||
(DataFormat, ushort)? jpgFormat = null;
|
||||
(DataFormat, ushort)? dibFormat = null;
|
||||
(DataFormat, ushort)? bitFormat = null;
|
||||
|
||||
foreach (var currentFormat in s_formats)
|
||||
{
|
||||
if (currentFormat.Id == (ushort)UnmanagedMethods.ClipboardFormat.CF_DIB)
|
||||
dibFormat = currentFormat;
|
||||
else if (currentFormat.Id == (ushort)UnmanagedMethods.ClipboardFormat.CF_BITMAP)
|
||||
bitFormat = currentFormat;
|
||||
else if (currentFormat.Format.Identifier == PngFormatMimeType)
|
||||
pngFormat = currentFormat;
|
||||
else if (currentFormat.Format.Identifier == JpegFormatMimeType)
|
||||
jpgFormat = currentFormat;
|
||||
}
|
||||
var imageFormatId = dibFormat?.Item2 ?? bitFormat?.Item2 ?? pngFormat?.Item2 ?? jpgFormat?.Item2 ?? 0;
|
||||
var imageFormatId = pngFormat?.Item2 ?? dibFormat?.Item2 ?? 0;
|
||||
|
||||
if(imageFormatId != 0)
|
||||
if (imageFormatId != 0)
|
||||
{
|
||||
return imageFormatId;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.MicroCom;
|
||||
using Avalonia.Platform.Storage;
|
||||
using static Avalonia.Win32.Interop.UnmanagedMethods;
|
||||
using FORMATETC = Avalonia.Win32.Interop.UnmanagedMethods.FORMATETC;
|
||||
using STGMEDIUM = Avalonia.Win32.Interop.UnmanagedMethods.STGMEDIUM;
|
||||
@@ -33,7 +32,23 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
|
||||
|
||||
public FormatEnumerator(IReadOnlyList<DataFormat> dataFormats)
|
||||
{
|
||||
_formats = dataFormats.Select(OleDataObjectHelper.ToFormatEtc).ToArray();
|
||||
List<FORMATETC> formats = new List<FORMATETC>();
|
||||
|
||||
if (dataFormats.Contains(DataFormat.Bitmap))
|
||||
{
|
||||
// We add extra formats for bitmaps
|
||||
formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.PngMimeDataFormat));
|
||||
formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.PngSystemDataFormat));
|
||||
formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.DibDataFormat));
|
||||
formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.DibV5DataFormat));
|
||||
formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.HBitmapDataFormat, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
formats.AddRange(dataFormats.Select(x => OleDataObjectHelper.ToFormatEtc(x)));
|
||||
}
|
||||
|
||||
_formats = formats.ToArray();
|
||||
_current = 0;
|
||||
}
|
||||
|
||||
@@ -113,9 +128,18 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
|
||||
if (!ValidateFormat(format, out var result, out var dataFormat))
|
||||
return result;
|
||||
|
||||
*medium = default;
|
||||
medium->tymed = TYMED.TYMED_HGLOBAL;
|
||||
return OleDataObjectHelper.WriteDataToHGlobal(DataTransfer, dataFormat, ref medium->unionmember);
|
||||
if (format->tymed == TYMED.TYMED_GDI)
|
||||
{
|
||||
*medium = default;
|
||||
medium->tymed = TYMED.TYMED_GDI;
|
||||
return OleDataObjectHelper.WriteDataToGdi(DataTransfer, dataFormat, ref medium->unionmember);
|
||||
}
|
||||
else
|
||||
{
|
||||
*medium = default;
|
||||
medium->tymed = TYMED.TYMED_HGLOBAL;
|
||||
return OleDataObjectHelper.WriteDataToHGlobal(DataTransfer, dataFormat, ref medium->unionmember);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe uint Win32Com.IDataObject.GetDataHere(FORMATETC* format, STGMEDIUM* medium)
|
||||
@@ -142,7 +166,7 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
|
||||
{
|
||||
dataFormat = null;
|
||||
|
||||
if (!format->tymed.HasAllFlags(TYMED.TYMED_HGLOBAL))
|
||||
if (!(format->tymed.HasAllFlags(TYMED.TYMED_HGLOBAL) || format->cfFormat == (ushort)ClipboardFormat.CF_BITMAP && format->tymed == TYMED.TYMED_GDI))
|
||||
{
|
||||
result = DV_E_TYMED;
|
||||
dataFormat = null;
|
||||
@@ -162,7 +186,7 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
|
||||
}
|
||||
|
||||
dataFormat = ClipboardFormatRegistry.GetFormatById(format->cfFormat);
|
||||
if (!DataTransfer.Contains(dataFormat))
|
||||
if (!DataTransfer.Contains(dataFormat) && !ClipboardFormatRegistry.ImageFormats.Contains(dataFormat))
|
||||
{
|
||||
result = DV_E_FORMATETC;
|
||||
return false;
|
||||
|
||||
@@ -895,6 +895,23 @@ namespace Avalonia.Win32.Interop
|
||||
BI_PNG = 5
|
||||
}
|
||||
|
||||
public enum BitmapColorSpace : uint
|
||||
{
|
||||
LCS_CALIBRATED_RGB = 0,
|
||||
LCS_sRGB = 0x73524742,
|
||||
LCS_WINDOWS_COLOR_SPACE = 0x57696E20,
|
||||
PROFILE_LINKED = 0x4C494E4B,
|
||||
PROFILE_EMBEDDED = 0x4D424544
|
||||
}
|
||||
|
||||
public enum BitmapIntent : uint
|
||||
{
|
||||
LCS_GM_ABS_COLORIMETRIC = 1,
|
||||
LCS_GM_BUSINESS = 2,
|
||||
LCS_GM_GRAPHICS = 4,
|
||||
LCS_GM_IMAGES = 8,
|
||||
}
|
||||
|
||||
public enum DIBColorTable
|
||||
{
|
||||
DIB_RGB_COLORS = 0, /* color table in RGBs */
|
||||
@@ -1101,7 +1118,7 @@ namespace Avalonia.Win32.Interop
|
||||
public int biHeight;
|
||||
public ushort biPlanes;
|
||||
public ushort biBitCount;
|
||||
public uint biCompression;
|
||||
public BitmapCompressionMode biCompression;
|
||||
public uint biSizeImage;
|
||||
public int biXPelsPerMeter;
|
||||
public int biYPelsPerMeter;
|
||||
@@ -1114,6 +1131,56 @@ namespace Avalonia.Win32.Interop
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct BITMAPV5HEADER
|
||||
{
|
||||
public uint bV5Size;
|
||||
public int bV5Width;
|
||||
public int bV5Height;
|
||||
public ushort bV5Planes;
|
||||
public ushort bV5BitCount;
|
||||
public BitmapCompressionMode bV5Compression;
|
||||
public uint bV5SizeImage;
|
||||
public int bV5XPelsPerMeter;
|
||||
public int bV5YPelsPerMeter;
|
||||
public uint bV5ClrUsed;
|
||||
public uint bV5ClrImportant;
|
||||
public uint bV5RedMask;
|
||||
public uint bV5GreenMask;
|
||||
public uint bV5BlueMask;
|
||||
public uint bV5AlphaMask;
|
||||
public BitmapColorSpace bV5CSType;
|
||||
public CIEXYZTRIPLE bV5Endpoints;
|
||||
public uint bV5GammaRed;
|
||||
public uint bV5GammaGreen;
|
||||
public uint bV5GammaBlue;
|
||||
public BitmapIntent bV5Intent;
|
||||
public uint bV5ProfileData;
|
||||
public uint bV5ProfileSize;
|
||||
public uint bV5Reserved;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
bV5Size = (uint)sizeof(BITMAPV5HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct CIEXYZTRIPLE
|
||||
{
|
||||
public CIEXYZ ciexyzRed;
|
||||
public CIEXYZ ciexyzGreen;
|
||||
public CIEXYZ ciexyzBlue;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct CIEXYZ
|
||||
{
|
||||
public int ciexyzX;
|
||||
public int ciexyzY;
|
||||
public int ciexyzZ;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct BITMAPINFO
|
||||
{
|
||||
@@ -1134,9 +1201,10 @@ namespace Avalonia.Win32.Interop
|
||||
public uint biClrUsed;
|
||||
public uint biClrImportant;
|
||||
//}
|
||||
public uint biRedMask;
|
||||
public uint biGreenMask;
|
||||
public uint biBlueMask;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
|
||||
public uint[] cols;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
@@ -1208,6 +1276,15 @@ namespace Avalonia.Win32.Interop
|
||||
uint uStartScan, uint cScanLines,
|
||||
IntPtr lpvBits, [In] ref BITMAPINFO lpbmi, uint fuColorUse);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern int SetDIBits(IntPtr hdc, IntPtr hbm, uint start, uint cLines, IntPtr lpBits, in BITMAPINFO lpbmi, uint fuColorUse);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern int SetDIBits(IntPtr hdc, IntPtr hbm, uint start, uint cLines, IntPtr lpBits, in BITMAPINFOHEADER lpbmi, uint fuColorUse);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern int SetDIBits(IntPtr hdc, IntPtr hbm, uint start, uint cLines, IntPtr lpBits, in BITMAPV5HEADER lpbmi, uint fuColorUse);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = false, ExactSpelling = true)]
|
||||
public static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2);
|
||||
|
||||
@@ -1697,13 +1774,25 @@ namespace Avalonia.Win32.Interop
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest, uint
|
||||
dwWidth, uint dwHeight, int XSrc, int YSrc, uint uStartScan, uint cScanLines,
|
||||
IntPtr lpvBits, [In] ref BITMAPINFOHEADER lpbmi, uint fuColorUse);
|
||||
IntPtr lpvBits, [In] ref BITMAPINFOHEADER lpbmi, DIBColorTable fuColorUse);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern IntPtr CreateDIBSection(IntPtr hDC, ref BITMAPINFOHEADER pBitmapInfo, int un, out IntPtr lplpVoid, IntPtr handle, int dw);
|
||||
public static extern IntPtr CreateDIBSection(IntPtr hDC, ref BITMAPINFOHEADER pBitmapInfo, DIBColorTable usage, out IntPtr lplpVoid, IntPtr handle, int dw);
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern IntPtr CreateDIBSection(IntPtr hDC, in BITMAPV5HEADER pBitmapInfo, DIBColorTable usage, out IntPtr lplpVoid, IntPtr handle, int dw);
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern IntPtr CreateDIBitmap(IntPtr hDC, in BITMAPV5HEADER pBitmapInfo, int flInit, IntPtr lplpVoid, in BITMAPINFO pbmi, DIBColorTable iUsage);
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern bool GdiFlush();
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern bool BitBlt(IntPtr hDC, int x, int y, int cx, int cy, IntPtr hdcSrc, int x1, int y1, uint rop);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int cx, int cy);
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern IntPtr CreateBitmap(int width, int height, int planes, int bitCount, IntPtr data);
|
||||
[DllImport("gdi32.dll")]
|
||||
@@ -2230,6 +2319,7 @@ namespace Avalonia.Win32.Interop
|
||||
/// A handle to type HDROP that identifies a list of files.
|
||||
/// </summary>
|
||||
CF_HDROP = 15,
|
||||
CF_DIBV5 = 17,
|
||||
}
|
||||
|
||||
public struct MSG
|
||||
|
||||
@@ -7,10 +7,10 @@ using System.Runtime.InteropServices.ComTypes;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Logging;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Platform.Storage.FileIO;
|
||||
using Avalonia.Utilities;
|
||||
using Avalonia.Win32.Interop;
|
||||
using static Avalonia.Win32.Interop.UnmanagedMethods;
|
||||
using FORMATETC = Avalonia.Win32.Interop.UnmanagedMethods.FORMATETC;
|
||||
using STGMEDIUM = Avalonia.Win32.Interop.UnmanagedMethods.STGMEDIUM;
|
||||
@@ -24,14 +24,14 @@ internal static class OleDataObjectHelper
|
||||
{
|
||||
private const int SRCCOPY = 0x00CC0020;
|
||||
|
||||
public static FORMATETC ToFormatEtc(this DataFormat format)
|
||||
public static FORMATETC ToFormatEtc(this DataFormat format, bool isGdi = false)
|
||||
=> new()
|
||||
{
|
||||
cfFormat = ClipboardFormatRegistry.GetFormatId(format),
|
||||
dwAspect = DVASPECT.DVASPECT_CONTENT,
|
||||
ptd = IntPtr.Zero,
|
||||
lindex = -1,
|
||||
tymed = TYMED.TYMED_HGLOBAL
|
||||
tymed = isGdi ? TYMED.TYMED_GDI : TYMED.TYMED_HGLOBAL
|
||||
};
|
||||
|
||||
public static unsafe object? TryGet(this Win32Com.IDataObject oleDataObject, DataFormat format)
|
||||
@@ -327,42 +327,91 @@ internal static class OleDataObjectHelper
|
||||
return WriteFileNamesToHGlobal(ref hGlobal, fileNames);
|
||||
}
|
||||
|
||||
if (DataFormat.Bitmap.Equals(format))
|
||||
if (ClipboardFormatRegistry.DibDataFormat.Equals(format)
|
||||
|| ClipboardFormatRegistry.DibV5DataFormat.Equals(format))
|
||||
{
|
||||
var bitmap = dataTransfer.TryGetValue(DataFormat.Bitmap);
|
||||
if (bitmap != null)
|
||||
{
|
||||
bool isV5 = ClipboardFormatRegistry.DibV5DataFormat.Equals(format);
|
||||
var pixelSize = bitmap.PixelSize;
|
||||
var bpp = bitmap.Format?.BitsPerPixel ?? 0;
|
||||
var stride = ((bitmap.Format?.BitsPerPixel ?? 0) / 8) * pixelSize.Width;
|
||||
var buffer = new byte[stride * pixelSize.Height];
|
||||
fixed (byte* bytes = buffer)
|
||||
{
|
||||
bitmap.CopyPixels(new PixelRect(pixelSize), (IntPtr)bytes, buffer.Length, stride);
|
||||
|
||||
var infoHeader = new BITMAPINFOHEADER()
|
||||
if (!isV5)
|
||||
{
|
||||
biSizeImage = (uint)buffer.Length,
|
||||
biWidth = pixelSize.Width,
|
||||
biHeight = -pixelSize.Height,
|
||||
biBitCount = (ushort)(bitmap.Format?.BitsPerPixel ?? 0),
|
||||
biPlanes = 1,
|
||||
biCompression = (uint)BitmapCompressionMode.BI_RGB
|
||||
};
|
||||
infoHeader.Init();
|
||||
var infoHeader = new BITMAPINFOHEADER()
|
||||
{
|
||||
biSizeImage = (uint)buffer.Length,
|
||||
biWidth = pixelSize.Width,
|
||||
biHeight = -pixelSize.Height,
|
||||
biBitCount = (ushort)bpp,
|
||||
biPlanes = 1,
|
||||
biCompression = BitmapCompressionMode.BI_RGB,
|
||||
};
|
||||
infoHeader.Init();
|
||||
|
||||
var imageData = new byte[infoHeader.biSize + infoHeader.biSizeImage];
|
||||
var imageData = new byte[infoHeader.biSize + infoHeader.biSizeImage];
|
||||
|
||||
fixed (byte* image = imageData)
|
||||
fixed (byte* image = imageData)
|
||||
{
|
||||
Marshal.StructureToPtr(infoHeader, (IntPtr)image, false);
|
||||
new Span<byte>(bytes, buffer.Length).CopyTo(new Span<byte>((image + infoHeader.biSize), buffer.Length));
|
||||
|
||||
return WriteBytesToHGlobal(ref hGlobal, imageData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Marshal.StructureToPtr(infoHeader, (IntPtr)image, false);
|
||||
new Span<byte>(bytes, buffer.Length).CopyTo(new Span<byte>((image + infoHeader.biSize), buffer.Length));
|
||||
var infoHeader = new BITMAPV5HEADER()
|
||||
{
|
||||
bV5Width = pixelSize.Width,
|
||||
bV5Height = -pixelSize.Height,
|
||||
bV5Planes = 1,
|
||||
bV5BitCount = (ushort)bpp,
|
||||
bV5Compression = bpp > 16 ? BitmapCompressionMode.BI_BITFIELDS : BitmapCompressionMode.BI_RGB,
|
||||
bV5SizeImage = (uint)buffer.Length,
|
||||
bV5RedMask = GetRedMask(bitmap),
|
||||
bV5BlueMask = GetBlueMask(bitmap),
|
||||
bV5GreenMask = GetGreenMask(bitmap),
|
||||
bV5AlphaMask = GetAlphaMask(bitmap),
|
||||
bV5CSType = BitmapColorSpace.LCS_sRGB,
|
||||
bV5Intent = BitmapIntent.LCS_GM_ABS_COLORIMETRIC
|
||||
};
|
||||
infoHeader.Init();
|
||||
|
||||
return WriteBytesToHGlobal(ref hGlobal, imageData);
|
||||
var imageData = new byte[infoHeader.bV5Size + infoHeader.bV5SizeImage];
|
||||
|
||||
fixed (byte* image = imageData)
|
||||
{
|
||||
Marshal.StructureToPtr(infoHeader, (IntPtr)image, false);
|
||||
new Span<byte>(bytes, buffer.Length).CopyTo(new Span<byte>((image + infoHeader.bV5Size), buffer.Length));
|
||||
|
||||
return WriteBytesToHGlobal(ref hGlobal, imageData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ClipboardFormatRegistry.PngSystemDataFormat.Equals(format)
|
||||
|| ClipboardFormatRegistry.PngMimeDataFormat.Equals(format))
|
||||
{
|
||||
var bitmap = dataTransfer.TryGetValue(DataFormat.Bitmap);
|
||||
if (bitmap != null)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
bitmap.Save(stream);
|
||||
|
||||
return WriteBytesToHGlobal(ref hGlobal, stream.ToArray().AsSpan());
|
||||
}
|
||||
return DV_E_FORMATETC;
|
||||
}
|
||||
|
||||
if (format is DataFormat<string> stringFormat)
|
||||
{
|
||||
return dataTransfer.TryGetValue(stringFormat) is { } stringValue ?
|
||||
@@ -383,6 +432,145 @@ internal static class OleDataObjectHelper
|
||||
return DV_E_FORMATETC;
|
||||
}
|
||||
|
||||
private static uint GetAlphaMask(Bitmap? bitmap)
|
||||
{
|
||||
return bitmap?.Format?.FormatEnum switch
|
||||
{
|
||||
PixelFormatEnum.Rgba8888 => 0xff000000,
|
||||
PixelFormatEnum.Bgra8888 => 0xff000000,
|
||||
PixelFormatEnum.Rgb565 => 0,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
|
||||
private static uint GetGreenMask(Bitmap? bitmap)
|
||||
{
|
||||
return bitmap?.Format?.FormatEnum switch
|
||||
{
|
||||
PixelFormatEnum.Rgba8888 => 0x0000ff00,
|
||||
PixelFormatEnum.Bgra8888 => 0x0000ff00,
|
||||
PixelFormatEnum.Rgb565 => 0b0000011111100000,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
|
||||
private static uint GetBlueMask(Bitmap? bitmap)
|
||||
{
|
||||
return bitmap?.Format?.FormatEnum switch
|
||||
{
|
||||
PixelFormatEnum.Rgba8888 => 0x00ff0000,
|
||||
PixelFormatEnum.Bgra8888 => 0x000000ff,
|
||||
PixelFormatEnum.Rgb565 => 0b1111100000000000,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
|
||||
private static uint GetRedMask(Bitmap? bitmap)
|
||||
{
|
||||
return bitmap?.Format?.FormatEnum switch
|
||||
{
|
||||
PixelFormatEnum.Rgba8888 => 0x000000ff,
|
||||
PixelFormatEnum.Bgra8888 => 0x00ff0000,
|
||||
PixelFormatEnum.Rgb565 => 0b0000000000011111,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
|
||||
public unsafe static uint WriteDataToGdi(IDataTransfer dataTransfer, DataFormat format, ref IntPtr hGlobalBitmap)
|
||||
{
|
||||
if (ClipboardFormatRegistry.HBitmapDataFormat.Equals(format))
|
||||
{
|
||||
var bitmap = dataTransfer.TryGetValue(DataFormat.Bitmap);
|
||||
if (bitmap != null)
|
||||
{
|
||||
var pixelSize = bitmap.PixelSize;
|
||||
var bpp = bitmap.Format?.BitsPerPixel ?? 0;
|
||||
var stride = (bpp / 8) * pixelSize.Width;
|
||||
var buffer = new byte[stride * pixelSize.Height];
|
||||
fixed (byte* bytes = buffer)
|
||||
{
|
||||
bitmap.CopyPixels(new PixelRect(pixelSize), (IntPtr)bytes, buffer.Length, stride);
|
||||
|
||||
IntPtr hdc = IntPtr.Zero, compatDc = IntPtr.Zero, desDc = IntPtr.Zero, hbitmap = IntPtr.Zero, section = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
hdc = GetDC(IntPtr.Zero);
|
||||
if (hdc == IntPtr.Zero)
|
||||
return DV_E_FORMATETC;
|
||||
|
||||
compatDc = CreateCompatibleDC(hdc);
|
||||
if (compatDc == IntPtr.Zero)
|
||||
return DV_E_FORMATETC;
|
||||
|
||||
desDc = CreateCompatibleDC(hdc);
|
||||
if (desDc == IntPtr.Zero)
|
||||
return DV_E_FORMATETC;
|
||||
|
||||
var bitmapInfoHeader = new BITMAPV5HEADER()
|
||||
{
|
||||
bV5Width = pixelSize.Width,
|
||||
bV5Height = -pixelSize.Height,
|
||||
bV5Planes = 1,
|
||||
bV5BitCount = (ushort)bpp,
|
||||
bV5Compression = BitmapCompressionMode.BI_BITFIELDS,
|
||||
bV5SizeImage = (uint)buffer.Length,
|
||||
bV5RedMask = GetRedMask(bitmap),
|
||||
bV5BlueMask = GetBlueMask(bitmap),
|
||||
bV5GreenMask = GetGreenMask(bitmap),
|
||||
bV5AlphaMask = GetAlphaMask(bitmap),
|
||||
bV5CSType = BitmapColorSpace.LCS_sRGB,
|
||||
bV5Intent = BitmapIntent.LCS_GM_ABS_COLORIMETRIC,
|
||||
};
|
||||
|
||||
bitmapInfoHeader.Init();
|
||||
|
||||
section = CreateDIBSection(compatDc, bitmapInfoHeader, 0, out var lbBits, IntPtr.Zero, 0);
|
||||
if (section == IntPtr.Zero)
|
||||
return DV_E_FORMATETC;
|
||||
|
||||
SelectObject(compatDc, section);
|
||||
|
||||
Marshal.Copy(buffer, 0, lbBits, buffer.Length);
|
||||
|
||||
hbitmap = CreateCompatibleBitmap(desDc, pixelSize.Width, pixelSize.Height);
|
||||
|
||||
SelectObject(desDc, hbitmap);
|
||||
|
||||
if (!BitBlt(desDc, 0, 0, pixelSize.Width, pixelSize.Height, compatDc, 0, 0, SRCCOPY))
|
||||
{
|
||||
return DV_E_FORMATETC;
|
||||
}
|
||||
|
||||
hGlobalBitmap = hbitmap;
|
||||
|
||||
GdiFlush();
|
||||
|
||||
return (uint)HRESULT.S_OK;
|
||||
}
|
||||
finally
|
||||
{
|
||||
SelectObject(compatDc, IntPtr.Zero);
|
||||
SelectObject(desDc, IntPtr.Zero);
|
||||
|
||||
if (desDc != IntPtr.Zero)
|
||||
ReleaseDC(IntPtr.Zero, desDc);
|
||||
|
||||
if (compatDc != IntPtr.Zero)
|
||||
ReleaseDC(IntPtr.Zero, compatDc);
|
||||
|
||||
if (hdc != IntPtr.Zero)
|
||||
ReleaseDC(IntPtr.Zero, hdc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.TryGet(LogEventLevel.Warning, LogArea.Win32Platform)
|
||||
?.Log(null, "Unsupported gdi data format {Format}", format);
|
||||
|
||||
return DV_E_FORMATETC;
|
||||
}
|
||||
|
||||
private static unsafe uint WriteStringToHGlobal(ref IntPtr hGlobal, string data)
|
||||
{
|
||||
var requiredSize = (data.Length + 1) * sizeof(char);
|
||||
|
||||
@@ -32,21 +32,21 @@ internal sealed class OleDataObjectToDataTransferWrapper(Win32Com.IDataObject ol
|
||||
while (Next(enumFormat) is { } format)
|
||||
formats.Add(format);
|
||||
|
||||
bool hasSupportedFormat = false;
|
||||
bool hasSupportedImageFormat = false;
|
||||
|
||||
foreach (var format in formats)
|
||||
{
|
||||
if (format.Identifier is ClipboardFormatRegistry.DibFormat
|
||||
or ClipboardFormatRegistry.BitmapFormat
|
||||
or ClipboardFormatRegistry.PngFormatMimeType
|
||||
or ClipboardFormatRegistry.JpegFormatMimeType)
|
||||
or ClipboardFormatRegistry.PngFormatMimeType
|
||||
or ClipboardFormatRegistry.PngFormatSystemType)
|
||||
{
|
||||
hasSupportedFormat = true;
|
||||
hasSupportedImageFormat = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSupportedFormat)
|
||||
if (hasSupportedImageFormat)
|
||||
{
|
||||
formats.Add(DataFormat.Bitmap);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user