Browse Source

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>
pull/20295/head
Emmanuel Hansen 2 months ago
committed by GitHub
parent
commit
9623669c41
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 26
      src/Windows/Avalonia.Win32/ClipboardFormatRegistry.cs
  2. 38
      src/Windows/Avalonia.Win32/DataTransferToOleDataObjectWrapper.cs
  3. 100
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  4. 230
      src/Windows/Avalonia.Win32/OleDataObjectHelper.cs
  5. 10
      src/Windows/Avalonia.Win32/OleDataObjectToDataTransferWrapper.cs

26
src/Windows/Avalonia.Win32/ClipboardFormatRegistry.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Media.Imaging;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
@ -12,17 +13,28 @@ namespace Avalonia.Win32
private const int MaxFormatNameLength = 260; private const int MaxFormatNameLength = 260;
private const string AppPrefix = "avn-app-fmt:"; private const string AppPrefix = "avn-app-fmt:";
public const string PngFormatMimeType = "image/png"; 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 BitmapFormat = "CF_BITMAP";
public const string DibFormat = "CF_DIB"; public const string DibFormat = "CF_DIB";
public const string DibV5Format = "CF_DIBV5";
private static readonly List<(DataFormat Format, ushort Id)> s_formats = []; 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() static ClipboardFormatRegistry()
{ {
AddDataFormat(DataFormat.Text, (ushort)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT); AddDataFormat(DataFormat.Text, (ushort)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT);
AddDataFormat(DataFormat.Text, (ushort)UnmanagedMethods.ClipboardFormat.CF_TEXT); AddDataFormat(DataFormat.Text, (ushort)UnmanagedMethods.ClipboardFormat.CF_TEXT);
AddDataFormat(DataFormat.File, (ushort)UnmanagedMethods.ClipboardFormat.CF_HDROP); 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) private static void AddDataFormat(DataFormat format, ushort id)
@ -62,24 +74,18 @@ namespace Avalonia.Win32
if (DataFormat.Bitmap.Equals(format)) if (DataFormat.Bitmap.Equals(format))
{ {
(DataFormat, ushort)? pngFormat = null; (DataFormat, ushort)? pngFormat = null;
(DataFormat, ushort)? jpgFormat = null;
(DataFormat, ushort)? dibFormat = null; (DataFormat, ushort)? dibFormat = null;
(DataFormat, ushort)? bitFormat = null;
foreach (var currentFormat in s_formats) foreach (var currentFormat in s_formats)
{ {
if (currentFormat.Id == (ushort)UnmanagedMethods.ClipboardFormat.CF_DIB) if (currentFormat.Id == (ushort)UnmanagedMethods.ClipboardFormat.CF_DIB)
dibFormat = currentFormat; dibFormat = currentFormat;
else if (currentFormat.Id == (ushort)UnmanagedMethods.ClipboardFormat.CF_BITMAP)
bitFormat = currentFormat;
else if (currentFormat.Format.Identifier == PngFormatMimeType) else if (currentFormat.Format.Identifier == PngFormatMimeType)
pngFormat = currentFormat; 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; return imageFormatId;
} }

38
src/Windows/Avalonia.Win32/DataTransferToOleDataObjectWrapper.cs

@ -6,7 +6,6 @@ using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes; using System.Runtime.InteropServices.ComTypes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.MicroCom; using Avalonia.MicroCom;
using Avalonia.Platform.Storage;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
using FORMATETC = Avalonia.Win32.Interop.UnmanagedMethods.FORMATETC; using FORMATETC = Avalonia.Win32.Interop.UnmanagedMethods.FORMATETC;
using STGMEDIUM = Avalonia.Win32.Interop.UnmanagedMethods.STGMEDIUM; using STGMEDIUM = Avalonia.Win32.Interop.UnmanagedMethods.STGMEDIUM;
@ -33,7 +32,23 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
public FormatEnumerator(IReadOnlyList<DataFormat> dataFormats) 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; _current = 0;
} }
@ -113,9 +128,18 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
if (!ValidateFormat(format, out var result, out var dataFormat)) if (!ValidateFormat(format, out var result, out var dataFormat))
return result; return result;
*medium = default; if (format->tymed == TYMED.TYMED_GDI)
medium->tymed = TYMED.TYMED_HGLOBAL; {
return OleDataObjectHelper.WriteDataToHGlobal(DataTransfer, dataFormat, ref medium->unionmember); *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) unsafe uint Win32Com.IDataObject.GetDataHere(FORMATETC* format, STGMEDIUM* medium)
@ -142,7 +166,7 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
{ {
dataFormat = null; 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; result = DV_E_TYMED;
dataFormat = null; dataFormat = null;
@ -162,7 +186,7 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
} }
dataFormat = ClipboardFormatRegistry.GetFormatById(format->cfFormat); dataFormat = ClipboardFormatRegistry.GetFormatById(format->cfFormat);
if (!DataTransfer.Contains(dataFormat)) if (!DataTransfer.Contains(dataFormat) && !ClipboardFormatRegistry.ImageFormats.Contains(dataFormat))
{ {
result = DV_E_FORMATETC; result = DV_E_FORMATETC;
return false; return false;

100
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -895,6 +895,23 @@ namespace Avalonia.Win32.Interop
BI_PNG = 5 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 public enum DIBColorTable
{ {
DIB_RGB_COLORS = 0, /* color table in RGBs */ DIB_RGB_COLORS = 0, /* color table in RGBs */
@ -1101,7 +1118,7 @@ namespace Avalonia.Win32.Interop
public int biHeight; public int biHeight;
public ushort biPlanes; public ushort biPlanes;
public ushort biBitCount; public ushort biBitCount;
public uint biCompression; public BitmapCompressionMode biCompression;
public uint biSizeImage; public uint biSizeImage;
public int biXPelsPerMeter; public int biXPelsPerMeter;
public int biYPelsPerMeter; 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)] [StructLayout(LayoutKind.Sequential)]
public struct BITMAPINFO public struct BITMAPINFO
{ {
@ -1134,9 +1201,10 @@ namespace Avalonia.Win32.Interop
public uint biClrUsed; public uint biClrUsed;
public uint biClrImportant; public uint biClrImportant;
//} //}
public uint biRedMask;
public uint biGreenMask;
public uint biBlueMask;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public uint[] cols;
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@ -1208,6 +1276,15 @@ namespace Avalonia.Win32.Interop
uint uStartScan, uint cScanLines, uint uStartScan, uint cScanLines,
IntPtr lpvBits, [In] ref BITMAPINFO lpbmi, uint fuColorUse); 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)] [DllImport("gdi32.dll", SetLastError = false, ExactSpelling = true)]
public static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2); public static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2);
@ -1697,13 +1774,25 @@ namespace Avalonia.Win32.Interop
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest, uint public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest, uint
dwWidth, uint dwHeight, int XSrc, int YSrc, uint uStartScan, uint cScanLines, 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)] [DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject); public static extern bool CloseHandle(IntPtr hObject);
[DllImport("gdi32.dll")] [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")] [DllImport("gdi32.dll")]
public static extern IntPtr CreateBitmap(int width, int height, int planes, int bitCount, IntPtr data); public static extern IntPtr CreateBitmap(int width, int height, int planes, int bitCount, IntPtr data);
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
@ -2230,6 +2319,7 @@ namespace Avalonia.Win32.Interop
/// A handle to type HDROP that identifies a list of files. /// A handle to type HDROP that identifies a list of files.
/// </summary> /// </summary>
CF_HDROP = 15, CF_HDROP = 15,
CF_DIBV5 = 17,
} }
public struct MSG public struct MSG

230
src/Windows/Avalonia.Win32/OleDataObjectHelper.cs

@ -7,10 +7,10 @@ using System.Runtime.InteropServices.ComTypes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO; using Avalonia.Platform.Storage.FileIO;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
using FORMATETC = Avalonia.Win32.Interop.UnmanagedMethods.FORMATETC; using FORMATETC = Avalonia.Win32.Interop.UnmanagedMethods.FORMATETC;
using STGMEDIUM = Avalonia.Win32.Interop.UnmanagedMethods.STGMEDIUM; using STGMEDIUM = Avalonia.Win32.Interop.UnmanagedMethods.STGMEDIUM;
@ -24,14 +24,14 @@ internal static class OleDataObjectHelper
{ {
private const int SRCCOPY = 0x00CC0020; private const int SRCCOPY = 0x00CC0020;
public static FORMATETC ToFormatEtc(this DataFormat format) public static FORMATETC ToFormatEtc(this DataFormat format, bool isGdi = false)
=> new() => new()
{ {
cfFormat = ClipboardFormatRegistry.GetFormatId(format), cfFormat = ClipboardFormatRegistry.GetFormatId(format),
dwAspect = DVASPECT.DVASPECT_CONTENT, dwAspect = DVASPECT.DVASPECT_CONTENT,
ptd = IntPtr.Zero, ptd = IntPtr.Zero,
lindex = -1, 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) public static unsafe object? TryGet(this Win32Com.IDataObject oleDataObject, DataFormat format)
@ -327,42 +327,91 @@ internal static class OleDataObjectHelper
return WriteFileNamesToHGlobal(ref hGlobal, fileNames); 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); var bitmap = dataTransfer.TryGetValue(DataFormat.Bitmap);
if (bitmap != null) if (bitmap != null)
{ {
bool isV5 = ClipboardFormatRegistry.DibV5DataFormat.Equals(format);
var pixelSize = bitmap.PixelSize; var pixelSize = bitmap.PixelSize;
var bpp = bitmap.Format?.BitsPerPixel ?? 0;
var stride = ((bitmap.Format?.BitsPerPixel ?? 0) / 8) * pixelSize.Width; var stride = ((bitmap.Format?.BitsPerPixel ?? 0) / 8) * pixelSize.Width;
var buffer = new byte[stride * pixelSize.Height]; var buffer = new byte[stride * pixelSize.Height];
fixed (byte* bytes = buffer) fixed (byte* bytes = buffer)
{ {
bitmap.CopyPixels(new PixelRect(pixelSize), (IntPtr)bytes, buffer.Length, stride); bitmap.CopyPixels(new PixelRect(pixelSize), (IntPtr)bytes, buffer.Length, stride);
var infoHeader = new BITMAPINFOHEADER() if (!isV5)
{ {
biSizeImage = (uint)buffer.Length, var infoHeader = new BITMAPINFOHEADER()
biWidth = pixelSize.Width, {
biHeight = -pixelSize.Height, biSizeImage = (uint)buffer.Length,
biBitCount = (ushort)(bitmap.Format?.BitsPerPixel ?? 0), biWidth = pixelSize.Width,
biPlanes = 1, biHeight = -pixelSize.Height,
biCompression = (uint)BitmapCompressionMode.BI_RGB biBitCount = (ushort)bpp,
}; biPlanes = 1,
infoHeader.Init(); biCompression = BitmapCompressionMode.BI_RGB,
};
var imageData = new byte[infoHeader.biSize + infoHeader.biSizeImage]; infoHeader.Init();
fixed (byte* image = imageData) var imageData = new byte[infoHeader.biSize + infoHeader.biSizeImage];
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); var infoHeader = new BITMAPV5HEADER()
new Span<byte>(bytes, buffer.Length).CopyTo(new Span<byte>((image + infoHeader.biSize), buffer.Length)); {
bV5Width = pixelSize.Width,
return WriteBytesToHGlobal(ref hGlobal, imageData); 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();
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) if (format is DataFormat<string> stringFormat)
{ {
return dataTransfer.TryGetValue(stringFormat) is { } stringValue ? return dataTransfer.TryGetValue(stringFormat) is { } stringValue ?
@ -383,6 +432,145 @@ internal static class OleDataObjectHelper
return DV_E_FORMATETC; 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) private static unsafe uint WriteStringToHGlobal(ref IntPtr hGlobal, string data)
{ {
var requiredSize = (data.Length + 1) * sizeof(char); var requiredSize = (data.Length + 1) * sizeof(char);

10
src/Windows/Avalonia.Win32/OleDataObjectToDataTransferWrapper.cs

@ -32,21 +32,21 @@ internal sealed class OleDataObjectToDataTransferWrapper(Win32Com.IDataObject ol
while (Next(enumFormat) is { } format) while (Next(enumFormat) is { } format)
formats.Add(format); formats.Add(format);
bool hasSupportedFormat = false; bool hasSupportedImageFormat = false;
foreach (var format in formats) foreach (var format in formats)
{ {
if (format.Identifier is ClipboardFormatRegistry.DibFormat if (format.Identifier is ClipboardFormatRegistry.DibFormat
or ClipboardFormatRegistry.BitmapFormat or ClipboardFormatRegistry.BitmapFormat
or ClipboardFormatRegistry.PngFormatMimeType or ClipboardFormatRegistry.PngFormatMimeType
or ClipboardFormatRegistry.JpegFormatMimeType) or ClipboardFormatRegistry.PngFormatSystemType)
{ {
hasSupportedFormat = true; hasSupportedImageFormat = true;
break; break;
} }
} }
if (hasSupportedFormat) if (hasSupportedImageFormat)
{ {
formats.Add(DataFormat.Bitmap); formats.Add(DataFormat.Bitmap);
} }

Loading…
Cancel
Save